From b94f0eb3e945626d6e8a697563ef7dc536e30fbb Mon Sep 17 00:00:00 2001 From: Elias Gabriel Date: Sat, 30 Dec 2023 11:24:02 -0500 Subject: [PATCH] change: move to pycasbin with async stubs fix: adapter returns --- .github/workflows/ci.yaml | 7 +- .pre-commit-config.yaml | 14 +-- README.md | 11 +- casbin_tortoise_adapter/adapter.py | 93 +++++++++++------ docker-compose.yaml | 2 +- pdm.lock | 158 +++++++++++++++++++++-------- pyproject.toml | 6 +- requirements.txt | 10 ++ tests/conftest.py | 14 +-- tests/test_adapter.py | 93 +++++++++-------- 10 files changed, 257 insertions(+), 151 deletions(-) create mode 100644 requirements.txt diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2ec3a58..78505b0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,15 +5,10 @@ on: [push, pull_request] jobs: test: runs-on: ubuntu-latest - continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: true matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] - experimental: [false] - include: - - python-version: "3.12" - experimental: true + python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] services: postgres: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c7aa4b..4f8f186 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,21 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.1.6 + rev: v0.1.9 hooks: - id: ruff args: ["--fix", "--exit-non-zero-on-fix"] - id: ruff-format - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.1 + - repo: local hooks: - id: mypy + name: mypy + language: system + entry: .venv/Scripts/python.exe -m mypy + types: [python] exclude: ^tests require_serial: true - args: ["--explicit-package-bases", "--check-untyped-defs"] - additional_dependencies: - ["asynccasbin<2.0.0,>=1.1.2", "tortoise-orm[accel]>=0.18.0"] - repo: https://github.com/pdm-project/pdm - rev: 2.10.4 + rev: 2.11.1 hooks: - id: pdm-lock-check - id: pdm-export diff --git a/README.md b/README.md index f088685..4abba80 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,7 @@ python3 -m pip install --user casbin-tortoise-adapter # or via your favorite dependency manager, like PDM ``` -The current supported databases are [limited by Tortoise ORM](https://tortoise.github.io/databases.html), and include: - -- PostgreSQL >= 9.4 (using `asyncpg`) -- SQLite (using `aiosqlite`) -- MySQL/MariaDB (using `asyncmy`) -- Microsoft SQL Server / Oracle (using `asyncodbc`) +The current supported databases are [limited by Tortoise ORM](https://tortoise.github.io/databases.html). ## Documentation @@ -32,7 +27,7 @@ A custom Model, combined with advanced configuration like show in the Tortoise O ## Basic example ```python -from casbin import Enforcer +from casbin import AsyncEnforcer from tortoise import Tortoise from casbin_tortoise_adapter import CasbinRule, TortoiseAdapter @@ -46,7 +41,7 @@ async def main() await Tortoise.generate_schemas() adapter = casbin_tortoise_adapter.TortoiseAdapter() - e = casbin.Enforcer('path/to/model.conf', adapter, True) + e = AsyncEnforcer('path/to/model.conf', adapter) sub = "alice" # the user that wants to access a resource. obj = "data1" # the resource that is going to be accessed. diff --git a/casbin_tortoise_adapter/adapter.py b/casbin_tortoise_adapter/adapter.py index 666041a..ab915b4 100644 --- a/casbin_tortoise_adapter/adapter.py +++ b/casbin_tortoise_adapter/adapter.py @@ -4,13 +4,13 @@ from dataclasses import asdict from typing import TYPE_CHECKING -from casbin.persist import ( - Adapter, - BatchAdapter, - FilteredAdapter, - load_policy_line, # pyright: ignore +from casbin.persist import load_policy_line +from casbin.persist.adapters.asyncio import ( + AsyncAdapter, + AsyncBatchAdapter, + AsyncFilteredAdapter, + AsyncUpdateAdapter, ) -from casbin.persist.adapters.update_adapter import UpdateAdapter from tortoise.expressions import Q from .model import CasbinRule @@ -27,11 +27,13 @@ class Assertion: policy: List[RuleType] -class TortoiseAdapter(BatchAdapter, UpdateAdapter, FilteredAdapter, Adapter): +class TortoiseAdapter( + AsyncBatchAdapter, AsyncUpdateAdapter, AsyncFilteredAdapter, AsyncAdapter +): """An async Casbin adapter for Tortoise ORM.""" def __init__(self, modelclass: Type[CasbinRule] = CasbinRule) -> None: - if not issubclass(modelclass, CasbinRule): # pyright: ignore + if not issubclass(modelclass, CasbinRule): raise TypeError( "The provided model class must be a subclass of CasbinRule!" ) @@ -39,14 +41,12 @@ def __init__(self, modelclass: Type[CasbinRule] = CasbinRule) -> None: self.modelclass: Type[CasbinRule] = modelclass self._filtered: bool = False - async def load_policy(self, model: Model) -> None: # pyright: ignore + async def load_policy(self, model: Model) -> None: """Loads all policy rules from storage.""" for line in await self.modelclass.all(): load_policy_line(str(line), model) - async def load_filtered_policy( # pyright: ignore - self, model: Model, filter: RuleFilter - ) -> None: + async def load_filtered_policy(self, model: Model, filter: RuleFilter) -> None: """Loads all policy rules that match the filter from storage.""" rules = await self.modelclass.filter( **{f"{f}__in": v for f, v in asdict(filter).items() if v} @@ -57,9 +57,9 @@ async def load_filtered_policy( # pyright: ignore self._filtered = True - async def save_policy(self, model: Model) -> None: # pyright: ignore + async def save_policy(self, model: Model) -> None: """Saves all policy rules to storage.""" - raw: Dict[str, Dict[str, Assertion]] = model.model # pyright: ignore + raw: Dict[str, Dict[str, Assertion]] = model.model rules: List[CasbinRule] = [ self._to_rule(ptype, rule) for sec in ("p", "g") @@ -71,46 +71,76 @@ async def save_policy(self, model: Model) -> None: # pyright: ignore async def add_policy( # pyright: ignore self, sec: str, ptype: str, rule: RuleType - ) -> None: + ) -> bool: """Saves a policy rule to storage.""" await self._to_rule(ptype, rule).save() + return True async def add_policies( # pyright: ignore self, sec: str, ptype: str, rules: List[RuleType] - ) -> None: + ) -> bool: """Saves policy rules to storage.""" batch = [self._to_rule(ptype, rule) for rule in rules] - await self.modelclass.bulk_create(batch) + rs: List[CasbinRule] = await self.modelclass.bulk_create(batch) # type: ignore + return len(rs) > 0 async def update_policy( # pyright: ignore self, sec: str, ptype: str, old_rule: RuleType, new_policy: RuleType - ) -> None: + ) -> bool: """ Updates a policy rule from storage. This is part of the Auto-Save feature. """ vs = {f"v{i}": rule for i, rule in enumerate(old_rule)} - r = self.modelclass.filter(ptype=ptype, **vs) - await r.update( + r = await self.modelclass.filter(ptype=ptype, **vs).update( **{ f"v{i}": (new_policy[i] if i < len(new_policy) else None) for i in range(6) } ) + return r > 0 - async def update_policies( + async def update_policies( # pyright: ignore self, sec: str, ptype: str, old_rules: List[RuleType], new_rules: List[RuleType], - ) -> None: + ) -> bool: """Updates the old rules with the new rules.""" - await asyncio.gather( + if not old_rules or not new_rules or (len(old_rules) != len(new_rules)): + raise ValueError( + "There must be at least one mapped pair of old and new rules." + ) + + rs = await asyncio.gather( *[ self.update_policy(sec, ptype, old_rule, new_rule) for old_rule, new_rule in zip(old_rules, new_rules) ] ) + return all(rs) + + async def update_filtered_policies( # pyright: ignore + self, + sec: str, + ptype: str, + new_rules: List[RuleType], + field_index: int, + *field_values: Tuple[str], + ) -> List[RuleType]: + """Updates the old filtered rules with the new rules.""" + if not (0 <= field_index <= 5) or not ( + 1 <= field_index + len(field_values) <= 6 + ): + return [] + + vs = {f"v{field_index + i}": v for i, v in enumerate(field_values) if v} + rs = await self.modelclass.filter(**vs).all() + old_rules = [self._from_rule(r) for r in rs] + + await self.update_policies(sec, ptype, old_rules, new_rules) + + return old_rules async def remove_policy( # pyright: ignore self, sec: str, ptype: str, rule: RuleType @@ -132,27 +162,28 @@ async def remove_filtered_policy( # pyright: ignore ): return False - r = 0 vs = {f"v{field_index + i}": v for i, v in enumerate(field_values) if v} - if vs: - r = await self.modelclass.filter(**vs).delete() - + r = await self.modelclass.filter(**vs).delete() return r > 0 async def remove_policies( # pyright: ignore self, sec: str, ptype: str, rules: List[RuleType] - ) -> None: + ) -> bool: """Removes policy rules from storage.""" if not rules: - return + return False qs = [Q(**{f"v{i}": v for i, v in enumerate(rule)}) for rule in rules] - await self.modelclass.filter(Q(*qs, join_type=Q.OR), ptype=ptype).delete() + r = await self.modelclass.filter(Q(*qs, join_type=Q.OR), ptype=ptype).delete() + return r > 0 - def is_filtered(self) -> bool: + def is_filtered(self) -> bool: # pyright: ignore """Returns if the loaded policy is filtered or not.""" return self._filtered def _to_rule(self, ptype: str, rule: RuleType) -> CasbinRule: kwargs: Dict[str, str] = {f"v{i}": v for i, v in enumerate(rule)} return self.modelclass(ptype=ptype, **kwargs) + + def _from_rule(self, rule: CasbinRule) -> RuleType: + return str(rule).split(", ")[1:] diff --git a/docker-compose.yaml b/docker-compose.yaml index 7eca8a0..d3323ca 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,7 +6,7 @@ services: POSTGRES_DB: "casbin_rule" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s + interval: 1s timeout: 5s retries: 5 diff --git a/pdm.lock b/pdm.lock index d3aab5b..8b8f4f1 100644 --- a/pdm.lock +++ b/pdm.lock @@ -4,8 +4,8 @@ [metadata] groups = ["default", "dev"] strategy = ["cross_platform"] -lock_version = "4.4" -content_hash = "sha256:5d1103fbac7e0fb0a996b1348388d2052c6300f07d8c16963c878bf05a23d935" +lock_version = "4.4.1" +content_hash = "sha256:884148a7ba850d84f9c15f9e8759dd0e69618962dea2e7767761725cd7bf9a85" [[package]] name = "aiosqlite" @@ -20,21 +20,6 @@ files = [ {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, ] -[[package]] -name = "asynccasbin" -version = "1.1.8" -requires_python = ">=3.7" -summary = "An async authorization library that supports access control models like ACL, RBAC, ABAC in Python" -dependencies = [ - "bracex==2.1.1", - "unicode==2.8", - "wcmatch==8.2", -] -files = [ - {file = "asynccasbin-1.1.8-py3-none-any.whl", hash = "sha256:a1aa60113e17e67bb026cfe7342a72f86b2359a6dcd1207deed74e0119416d81"}, - {file = "asynccasbin-1.1.8.tar.gz", hash = "sha256:2136d60f98abba5e38c2924a8dd7d161498d615461a37eb0de9c99659be8d760"}, -] - [[package]] name = "asyncpg" version = "0.28.0" @@ -87,13 +72,16 @@ files = [ ] [[package]] -name = "bracex" -version = "2.1.1" -requires_python = ">=3.6" -summary = "Bash style brace expander." +name = "casbin" +version = "1.34.0" +requires_python = ">=3.3" +summary = "An authorization library that supports access control models like ACL, RBAC, ABAC in Python" +dependencies = [ + "simpleeval>=0.9.11", +] files = [ - {file = "bracex-2.1.1-py3-none-any.whl", hash = "sha256:64e2a6d14de9c8e022cf40539ac8468ba7c4b99550a2b05fc87fd20e392e568f"}, - {file = "bracex-2.1.1.tar.gz", hash = "sha256:01f715cd0ed7a622ec8b32322e715813f7574de531f09b70f6f3b2c10f682425"}, + {file = "casbin-1.34.0-py3-none-any.whl", hash = "sha256:9dcb6df8464a7b19276461e56d912e09c42e160408dc04cc1336a08d674c76ca"}, + {file = "casbin-1.34.0.tar.gz", hash = "sha256:080c0a8e3b88b62a85a15bf29f2f5009d34bcd42a3f89f2448c6f4b828837776"}, ] [[package]] @@ -206,6 +194,56 @@ files = [ {file = "iso8601-1.1.0.tar.gz", hash = "sha256:32811e7b81deee2063ea6d2e94f8819a86d1f3811e49d23623a41fa832bef03f"}, ] +[[package]] +name = "mypy" +version = "1.4.1" +requires_python = ">=3.7" +summary = "Optional static typing for Python" +dependencies = [ + "mypy-extensions>=1.0.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "typed-ast<2,>=1.4.0; python_version < \"3.8\"", + "typing-extensions>=4.1.0", +] +files = [ + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +requires_python = ">=3.5" +summary = "Type system extensions for programs checked with the mypy type checker." +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "orjson" version = "3.9.7" @@ -349,6 +387,15 @@ files = [ {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] +[[package]] +name = "simpleeval" +version = "0.9.13" +summary = "A simple, safe single expression evaluator library." +files = [ + {file = "simpleeval-0.9.13-py2.py3-none-any.whl", hash = "sha256:22a2701a5006e4188d125d34accf2405c2c37c93f6b346f2484b6422415ae54a"}, + {file = "simpleeval-0.9.13.tar.gz", hash = "sha256:4a30f9cc01825fe4c719c785e3762623e350c4840d5e6855c2a8496baaa65fac"}, +] + [[package]] name = "tomli" version = "2.0.1" @@ -392,6 +439,49 @@ files = [ {file = "tortoise_orm-0.19.3.tar.gz", hash = "sha256:ca574bca5191f55608f9013314b1f5d1c6ffd4165a1fcc2f60f6c902f529b3b6"}, ] +[[package]] +name = "typed-ast" +version = "1.5.5" +requires_python = ">=3.6" +summary = "a fork of Python 2 and 3 ast modules with type comment support" +files = [ + {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, + {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, + {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, + {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, + {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, + {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, + {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, + {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, +] + [[package]] name = "typing-extensions" version = "4.7.1" @@ -402,15 +492,6 @@ files = [ {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] -[[package]] -name = "unicode" -version = "2.8" -summary = "Display unicode character properties" -files = [ - {file = "unicode-2.8-py2.py3-none-any.whl", hash = "sha256:a1bb93e2b9f68b9316734abcbc54fed78881df94268cd72d21b4f7a6be3ec970"}, - {file = "unicode-2.8.tar.gz", hash = "sha256:000f637428446f64352f72cb782e2a3dc587f14011ee4c9840657062ec3c27ed"}, -] - [[package]] name = "uvloop" version = "0.18.0" @@ -455,19 +536,6 @@ files = [ {file = "uvloop-0.18.0.tar.gz", hash = "sha256:d5d1135beffe9cd95d0350f19e2716bc38be47d5df296d7cc46e3b7557c0d1ff"}, ] -[[package]] -name = "wcmatch" -version = "8.2" -requires_python = ">=3.6" -summary = "Wildcard/glob file name matcher." -dependencies = [ - "bracex>=2.1.1", -] -files = [ - {file = "wcmatch-8.2-py3-none-any.whl", hash = "sha256:9146b1ab9354e0797ef6ef69bc89cb32cb9f46d1b9eeef69c559aeec8f3bffb6"}, - {file = "wcmatch-8.2.tar.gz", hash = "sha256:4d54ddb506c90b5a5bba3a96a1cfb0bb07127909e19046a71d689ddfb18c3617"}, -] - [[package]] name = "zipp" version = "3.15.0" diff --git a/pyproject.toml b/pyproject.toml index 2504229..fee98b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,8 +18,8 @@ authors = [ requires-python = ">=3.7,<4.0" dependencies = [ - "asynccasbin<2.0.0,>=1.1.2", - "tortoise-orm[accel]>=0.18.0" + "tortoise-orm[accel]>=0.18.0", + "casbin>=1.34.0", ] [project.urls] @@ -34,6 +34,7 @@ dev = [ "pluggy~=1.2.0", "pytest-asyncio~=0.21.0", "asyncpg~=0.28.0", + "mypy>=1.4.1", ] [tool.pytest.ini_options] @@ -42,7 +43,6 @@ testpaths = ["tests"] asyncio_mode = "auto" [tool.pyright] -reportMissingTypeStubs = "none" ignore = [ "tests" ] [tool.mypy] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..791da90 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +# This file is @generated by PDM. +# Please do not edit it manually. + +asyncpg~=0.28.0 +casbin>=1.34.0 +mypy>=1.4.1 +pluggy~=1.2.0 +pytest~=7.0 +pytest-asyncio~=0.21.0 +tortoise-orm[accel]>=0.18.0 diff --git a/tests/conftest.py b/tests/conftest.py index dd63dbf..dd8af20 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ import os import pytest -from casbin import Enforcer +from casbin import AsyncEnforcer from tortoise import Tortoise from casbin_tortoise_adapter import CasbinRule, TortoiseAdapter @@ -39,11 +39,6 @@ async def adapter(): await Tortoise.close_connections() -@pytest.fixture -def enforcer(adapter): - yield Enforcer("tests/rbac_model.conf", adapter) - - @pytest.fixture async def mock_data(): await CasbinRule.all().delete() @@ -56,3 +51,10 @@ async def mock_data(): CasbinRule(ptype="g", v0="alice", v1="data2_admin"), ] ) + + +@pytest.fixture +async def enforcer(adapter, mock_data): + e = AsyncEnforcer("tests/rbac_model.conf", adapter) + await e.load_policy() + yield e diff --git a/tests/test_adapter.py b/tests/test_adapter.py index d75824b..eca7947 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -11,10 +11,42 @@ class BadModel: TortoiseAdapter(modelclass=BadModel) -@pytest.mark.asyncio -async def test_enforcer_basic(mock_data, enforcer): - await enforcer.load_policy() +@pytest.mark.parametrize( + "rule,string", + ( + ( + ("p", "alice", "data1", "read"), + "p, alice, data1, read", + ), + ( + ("p", "bob", "data2", "write"), + "p, bob, data2, write", + ), + ( + ("p", "data2_admin", "data2", "read"), + "p, data2_admin, data2, read", + ), + ( + ("p", "data2_admin", "data2", "write"), + "p, data2_admin, data2, write", + ), + ( + ("g", "alice", "data2_admin", None), + "g, alice, data2_admin", + ), + ), +) +def test_str(rule, string): + ptype, v0, v1, v2 = rule + assert str(CasbinRule(ptype=ptype, v0=v0, v1=v1, v2=v2)) == string + + +def test_repr(): + rule = CasbinRule(ptype="p", v0="alice", v1="data1", v2="read") + assert repr(rule) == '' + +async def test_enforcer_basic(enforcer): assert enforcer.enforce("alice", "data1", "read") assert not enforcer.enforce("alice", "data1", "write") assert not enforcer.enforce("bob", "data1", "read") @@ -25,7 +57,6 @@ async def test_enforcer_basic(mock_data, enforcer): assert enforcer.enforce("alice", "data2", "write") -@pytest.mark.asyncio async def test_add_policy(enforcer): assert not enforcer.enforce("eve", "data3", "read") res = await enforcer.add_policies( @@ -36,15 +67,13 @@ async def test_add_policy(enforcer): assert enforcer.enforce("eve", "data4", "read") -@pytest.mark.asyncio async def test_add_policies(enforcer): - assert not enforcer.enforce("eve", "data3", "read") - res = await enforcer.add_permission_for_user("eve", "data3", "read") + assert not enforcer.enforce("dave", "data3", "read") + res = await enforcer.add_permission_for_user("dave", "data3", "read") assert res - assert enforcer.enforce("eve", "data3", "read") + assert enforcer.enforce("dave", "data3", "read") -@pytest.mark.asyncio async def test_save_policy(enforcer): assert not enforcer.enforce("alice", "data4", "read") @@ -55,7 +84,6 @@ async def test_save_policy(enforcer): assert enforcer.enforce("alice", "data4", "read") -@pytest.mark.asyncio async def test_remove_policy(enforcer): assert not enforcer.enforce("alice", "data5", "read") await enforcer.add_permission_for_user("alice", "data5", "read") @@ -64,7 +92,6 @@ async def test_remove_policy(enforcer): assert not enforcer.enforce("alice", "data5", "read") -@pytest.mark.asyncio async def test_remove_policies(enforcer): assert not enforcer.enforce("alice", "data5", "read") assert not enforcer.enforce("alice", "data6", "read") @@ -80,10 +107,7 @@ async def test_remove_policies(enforcer): assert not enforcer.enforce("alice", "data6", "read") -@pytest.mark.asyncio -async def test_remove_filtered_policy(mock_data, enforcer): - await enforcer.load_policy() - +async def test_remove_filtered_policy(enforcer): assert enforcer.enforce("alice", "data1", "read") await enforcer.remove_filtered_policy(1, "data1") assert not enforcer.enforce("alice", "data1", "read") @@ -104,28 +128,7 @@ async def test_remove_filtered_policy(mock_data, enforcer): assert not enforcer.enforce("alice", "data2", "write") -def test_str(enforcer): - rule = CasbinRule(ptype="p", v0="alice", v1="data1", v2="read") - assert str(rule) == "p, alice, data1, read" - rule = CasbinRule(ptype="p", v0="bob", v1="data2", v2="write") - assert str(rule) == "p, bob, data2, write" - rule = CasbinRule(ptype="p", v0="data2_admin", v1="data2", v2="read") - assert str(rule) == "p, data2_admin, data2, read" - rule = CasbinRule(ptype="p", v0="data2_admin", v1="data2", v2="write") - assert str(rule) == "p, data2_admin, data2, write" - rule = CasbinRule(ptype="g", v0="alice", v1="data2_admin") - assert str(rule) == "g, alice, data2_admin" - - -def test_repr(enforcer): - rule = CasbinRule(ptype="p", v0="alice", v1="data1", v2="read") - assert repr(rule) == '' - - -@pytest.mark.asyncio -async def test_filtered_policy(mock_data, enforcer): - await enforcer.load_policy() - +async def test_filtered_policy(enforcer): filter = RuleFilter() filter.ptype = ["p"] await enforcer.load_filtered_policy(filter) @@ -246,10 +249,7 @@ async def test_filtered_policy(mock_data, enforcer): assert enforcer.enforce("data2_admin", "data2", "write") -@pytest.mark.asyncio -async def test_update_policy(mock_data, enforcer): - await enforcer.load_policy() - +async def test_update_policy(enforcer): assert enforcer.enforce("alice", "data1", "read") await enforcer.update_policy( ["alice", "data1", "read"], ["alice", "data1", "no_read"] @@ -280,8 +280,7 @@ async def test_update_policy(mock_data, enforcer): assert not enforcer.enforce("bob", "data2", "write") -@pytest.mark.asyncio -async def test_update_policies(mock_data, enforcer): +async def test_update_policies(enforcer): old_rule_0 = ["alice", "data1", "read"] old_rule_1 = ["bob", "data2", "write"] old_rule_2 = ["data2_admin", "data2", "read"] @@ -295,7 +294,6 @@ async def test_update_policies(mock_data, enforcer): old_rules = [old_rule_0, old_rule_1, old_rule_2, old_rule_3] new_rules = [new_rule_0, new_rule_1, new_rule_2, new_rule_3] - await enforcer.load_policy() await enforcer.update_policies(old_rules, new_rules) assert not enforcer.enforce("alice", "data1", "read") @@ -309,3 +307,10 @@ async def test_update_policies(mock_data, enforcer): assert not enforcer.enforce("data2_admin", "data2", "write") assert enforcer.enforce("data2_admin", "data_test", "write") + + +async def test_update_filtered_policies(enforcer): + assert not enforcer.enforce("alice", "data1", "write") + + await enforcer.update_filtered_policies([["alice", "data1", "write"]], 0, "alice") + assert enforcer.enforce("alice", "data1", "write")