From 392773a7cd8511113cfbb758d25f16a6566f47db Mon Sep 17 00:00:00 2001 From: "qwen.ai[bot]" Date: Sun, 21 Jun 2026 06:56:28 +0000 Subject: [PATCH] Title: Refactor authorization infrastructure and update imports Key features implemented: - Updated import paths in CasbinAuthorizationService to reflect new core infrastructure location - Updated import paths in GetRoleQueryHandler to reflect new core infrastructure location - Updated import paths in dependency injection module to reflect new core infrastructure location - Updated import paths in permission router to reflect new core infrastructure location - Updated import paths in role router to reflect new core infrastructure location - Removed old authorization infrastructure modules and models from src/modules/authorization/infrastructure/ - Streamlined .gitignore file with consolidated ignore patterns The changes consolidate the authorization infrastructure under the core module and update all relevant imports across the application, removing redundant legacy infrastructure files. --- .gitignore | 56 +- .../services/casbin_authorization_service.py | 2 +- .../application/get_role/handler.py | 2 +- .../authorization/infrastructure/__init__.py | 0 .../infrastructure/models/__init__.py | 0 .../models/casbin_rule_model.py | 16 - .../infrastructure/models/permission_model.py | 20 - .../infrastructure/models/resource_model.py | 13 - .../infrastructure/models/role_model.py | 12 - .../models/role_permission_model.py | 20 - .../models/user_has_role_model.py | 16 - .../infrastructure/repositories/__init__.py | 0 .../repositories/casbin_policy_repository.py | 501 ------------------ .../infrastructure/services/__init__.py | 0 .../services/casbin_authorization_service.py | 137 ----- .../authorization/presentation/dependency.py | 4 +- .../presentation/routers/permission_router.py | 2 +- .../presentation/routers/role_router.py | 2 +- 18 files changed, 23 insertions(+), 780 deletions(-) delete mode 100644 src/modules/authorization/infrastructure/__init__.py delete mode 100644 src/modules/authorization/infrastructure/models/__init__.py delete mode 100644 src/modules/authorization/infrastructure/models/casbin_rule_model.py delete mode 100644 src/modules/authorization/infrastructure/models/permission_model.py delete mode 100644 src/modules/authorization/infrastructure/models/resource_model.py delete mode 100644 src/modules/authorization/infrastructure/models/role_model.py delete mode 100644 src/modules/authorization/infrastructure/models/role_permission_model.py delete mode 100644 src/modules/authorization/infrastructure/models/user_has_role_model.py delete mode 100644 src/modules/authorization/infrastructure/repositories/__init__.py delete mode 100644 src/modules/authorization/infrastructure/repositories/casbin_policy_repository.py delete mode 100644 src/modules/authorization/infrastructure/services/__init__.py delete mode 100644 src/modules/authorization/infrastructure/services/casbin_authorization_service.py diff --git a/.gitignore b/.gitignore index eca5b1b..b927ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,51 +1,29 @@ - -# Python +``` __pycache__/ *.pyc *.pyo *.pyd -.Python -*.so -*.egg-info/ -.eggs/ -.cache/ - -# Virtual environments -venv/ -.venv/ -env/ -ENV/ -.ENV - -# Build artifacts -build/ -dist/ -*.egg - -# Testing +.pytest_cache/ .coverage +coverage/ htmlcov/ -.pytest_cache/ -.mypy_cache/ - -# Logs *.log - -# Environment variables +*.tmp +*.swp +*.swo +.DS_Store +Thumbs.db .env .env.local *.env.* - -# IDE .vscode/ .idea/ -*.swp -*.swo -*.tmp - -# OS -.DS_Store -Thumbs.db - -# Coverage -coverage/ \ No newline at end of file +.mypy_cache/ +node_modules/ +venv/ +.venv/ +dist/ +build/ +target/ +.gradle/ +``` \ No newline at end of file diff --git a/src/core/authorization/infrastructure/services/casbin_authorization_service.py b/src/core/authorization/infrastructure/services/casbin_authorization_service.py index ca52751..b1d17ea 100644 --- a/src/core/authorization/infrastructure/services/casbin_authorization_service.py +++ b/src/core/authorization/infrastructure/services/casbin_authorization_service.py @@ -5,7 +5,7 @@ from src.modules.authorization.domain.services.authorization_service import ( AuthorizationService, ) -from src.modules.authorization.infrastructure.repositories.casbin_policy_repository import ( +from src.core.authorization.infrastructure.repositories.casbin_policy_repository import ( SQLAlchemyCasbinPolicyRepository, ) from src.modules.authorization.domain.entities.permission import Permission diff --git a/src/modules/authorization/application/get_role/handler.py b/src/modules/authorization/application/get_role/handler.py index 8a36d58..72da5e8 100644 --- a/src/modules/authorization/application/get_role/handler.py +++ b/src/modules/authorization/application/get_role/handler.py @@ -1,6 +1,6 @@ from src.modules.authorization.application.get_role.query import GetRoleQuery from src.modules.authorization.domain.entities.role import Role -from src.modules.authorization.infrastructure.repositories.casbin_policy_repository import ( +from src.core.authorization.infrastructure.repositories.casbin_policy_repository import ( CasbinPolicyRepository, ) diff --git a/src/modules/authorization/infrastructure/__init__.py b/src/modules/authorization/infrastructure/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/modules/authorization/infrastructure/models/__init__.py b/src/modules/authorization/infrastructure/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/modules/authorization/infrastructure/models/casbin_rule_model.py b/src/modules/authorization/infrastructure/models/casbin_rule_model.py deleted file mode 100644 index 97be56a..0000000 --- a/src/modules/authorization/infrastructure/models/casbin_rule_model.py +++ /dev/null @@ -1,16 +0,0 @@ -from sqlalchemy import String -from sqlalchemy.orm import Mapped, mapped_column - -from src.shared.database.model import Base - - -class CasbinRuleModel(Base): - __tablename__ = "casbin_rules" - - ptype: Mapped[str] = mapped_column(String(16), index=True) - v0: Mapped[str | None] = mapped_column(String(255), index=True, nullable=True) - v1: Mapped[str | None] = mapped_column(String(255), index=True, nullable=True) - v2: Mapped[str | None] = mapped_column(String(255), index=True, nullable=True) - v3: Mapped[str | None] = mapped_column(String(255), nullable=True) - v4: Mapped[str | None] = mapped_column(String(255), nullable=True) - v5: Mapped[str | None] = mapped_column(String(255), nullable=True) diff --git a/src/modules/authorization/infrastructure/models/permission_model.py b/src/modules/authorization/infrastructure/models/permission_model.py deleted file mode 100644 index ac3eab2..0000000 --- a/src/modules/authorization/infrastructure/models/permission_model.py +++ /dev/null @@ -1,20 +0,0 @@ -from uuid import UUID - -from sqlalchemy import String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column - -from src.shared.database.mixin.timestamp import SoftDeleteMixin, TimeStampMixin -from src.shared.database.model import Base - - -class PermissionModel(Base, TimeStampMixin, SoftDeleteMixin): - __tablename__ = "permissions" - __table_args__ = ( - UniqueConstraint("resource", "action", name="uq_permissions_resource_action"), - ) - - key: Mapped[str] = mapped_column(String(255), unique=True, index=True) - resource_id: Mapped[UUID] = mapped_column(index=True) - resource: Mapped[str] = mapped_column(String(100), index=True) - action: Mapped[str] = mapped_column(String(100), index=True) - description: Mapped[str | None] = mapped_column(String(255), nullable=True) diff --git a/src/modules/authorization/infrastructure/models/resource_model.py b/src/modules/authorization/infrastructure/models/resource_model.py deleted file mode 100644 index 95c2082..0000000 --- a/src/modules/authorization/infrastructure/models/resource_model.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import String -from sqlalchemy.orm import Mapped, mapped_column - -from src.shared.database.mixin.timestamp import SoftDeleteMixin, TimeStampMixin -from src.shared.database.model import Base - - -class AuthorizationResourceModel(Base, TimeStampMixin, SoftDeleteMixin): - __tablename__ = "authorization_resources" - - key: Mapped[str] = mapped_column(String(100), unique=True, index=True) - name: Mapped[str] = mapped_column(String(150), nullable=False) - description: Mapped[str | None] = mapped_column(String(255), nullable=True) diff --git a/src/modules/authorization/infrastructure/models/role_model.py b/src/modules/authorization/infrastructure/models/role_model.py deleted file mode 100644 index 7be2550..0000000 --- a/src/modules/authorization/infrastructure/models/role_model.py +++ /dev/null @@ -1,12 +0,0 @@ -from sqlalchemy import String -from sqlalchemy.orm import Mapped, mapped_column - -from src.shared.database.mixin.timestamp import SoftDeleteMixin, TimeStampMixin -from src.shared.database.model import Base - - -class RoleModel(Base, TimeStampMixin, SoftDeleteMixin): - __tablename__ = "roles" - - name: Mapped[str] = mapped_column(String(100), unique=True, index=True) - description: Mapped[str | None] = mapped_column(String(255), nullable=True) diff --git a/src/modules/authorization/infrastructure/models/role_permission_model.py b/src/modules/authorization/infrastructure/models/role_permission_model.py deleted file mode 100644 index a7d924c..0000000 --- a/src/modules/authorization/infrastructure/models/role_permission_model.py +++ /dev/null @@ -1,20 +0,0 @@ -from uuid import UUID - -from sqlalchemy import UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column - -from src.shared.database.model import Base - - -class RolePermissionModel(Base): - __tablename__ = "role_permissions" - __table_args__ = ( - UniqueConstraint( - "role_id", - "permission_id", - name="uq_role_permissions_role_id_permission_id", - ), - ) - - role_id: Mapped[UUID] = mapped_column(index=True) - permission_id: Mapped[UUID] = mapped_column(index=True) diff --git a/src/modules/authorization/infrastructure/models/user_has_role_model.py b/src/modules/authorization/infrastructure/models/user_has_role_model.py deleted file mode 100644 index 5849a38..0000000 --- a/src/modules/authorization/infrastructure/models/user_has_role_model.py +++ /dev/null @@ -1,16 +0,0 @@ -from uuid import UUID - -from sqlalchemy import UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column - -from src.shared.database.model import Base - - -class UserHasRoleModel(Base): - __tablename__ = "user_has_roles" - __table_args__ = ( - UniqueConstraint("user_id", "role_id", name="uq_user_has_roles_user_id_role_id"), - ) - - user_id: Mapped[UUID] = mapped_column(index=True) - role_id: Mapped[UUID] = mapped_column(index=True) diff --git a/src/modules/authorization/infrastructure/repositories/__init__.py b/src/modules/authorization/infrastructure/repositories/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/modules/authorization/infrastructure/repositories/casbin_policy_repository.py b/src/modules/authorization/infrastructure/repositories/casbin_policy_repository.py deleted file mode 100644 index 0b98669..0000000 --- a/src/modules/authorization/infrastructure/repositories/casbin_policy_repository.py +++ /dev/null @@ -1,501 +0,0 @@ -from datetime import datetime -from uuid import UUID - -from sqlalchemy import and_, delete, or_, select -from sqlalchemy.ext.asyncio import AsyncSession - -from src.core.authorization.infrastructure.models.casbin_rule_model import ( - CasbinRuleModel, -) -from src.core.authorization.infrastructure.models.permission_model import ( - PermissionModel, -) -from src.core.authorization.infrastructure.models.resource_model import ( - AuthorizationResourceModel, -) -from src.core.authorization.infrastructure.models.role_model import RoleModel -from src.core.authorization.infrastructure.models.role_permission_model import ( - RolePermissionModel, -) -from src.core.authorization.infrastructure.models.user_has_role_model import ( - UserHasRoleModel, -) -from src.modules.authorization import AuthorizationResource, Permission, Role -from src.shared.utils.cursor import CursorDirection - - -class SQLAlchemyCasbinPolicyRepository: - def __init__(self, db: AsyncSession): - self._db = db - - async def load_policy_lines(self) -> list[str]: - result = await self._db.execute(select(CasbinRuleModel)) - rules = result.scalars().all() - return [self._to_policy_line(rule) for rule in rules] - - async def list_policies(self) -> list[tuple[str, ...]]: - result = await self._db.execute(select(CasbinRuleModel)) - return [self._to_policy_tuple(rule) for rule in result.scalars().all()] - - async def add_policy(self, ptype: str, *values: str) -> None: - existing = await self._db.execute( - select(CasbinRuleModel).where( - CasbinRuleModel.ptype == ptype, - CasbinRuleModel.v0 == self._value_at(values, 0), - CasbinRuleModel.v1 == self._value_at(values, 1), - CasbinRuleModel.v2 == self._value_at(values, 2), - CasbinRuleModel.v3 == self._value_at(values, 3), - CasbinRuleModel.v4 == self._value_at(values, 4), - CasbinRuleModel.v5 == self._value_at(values, 5), - ) - ) - if existing.scalar_one_or_none(): - return - - self._db.add( - CasbinRuleModel( - ptype=ptype, - v0=self._value_at(values, 0), - v1=self._value_at(values, 1), - v2=self._value_at(values, 2), - v3=self._value_at(values, 3), - v4=self._value_at(values, 4), - v5=self._value_at(values, 5), - ) - ) - await self._db.flush() - - async def remove_policy(self, ptype: str, *values: str) -> None: - await self._db.execute( - delete(CasbinRuleModel).where( - CasbinRuleModel.ptype == ptype, - CasbinRuleModel.v0 == self._value_at(values, 0), - CasbinRuleModel.v1 == self._value_at(values, 1), - CasbinRuleModel.v2 == self._value_at(values, 2), - CasbinRuleModel.v3 == self._value_at(values, 3), - CasbinRuleModel.v4 == self._value_at(values, 4), - CasbinRuleModel.v5 == self._value_at(values, 5), - ) - ) - await self._db.flush() - - async def assign_role(self, subject: str, role: str) -> None: - role_model = await self._get_role_by_name(role) - if role_model is None: - raise ValueError(f"Role does not exist: {role}") - - user_id = UUID(subject) - existing_assignment = await self._db.execute( - select(UserHasRoleModel).where( - UserHasRoleModel.user_id == user_id, - UserHasRoleModel.role_id == role_model.id, - ) - ) - if existing_assignment.scalar_one_or_none() is None: - self._db.add(UserHasRoleModel(user_id=user_id, role_id=role_model.id)) - await self._db.flush() - - await self.add_policy("g", subject, role) - - async def get_roles_for_subject(self, subject: str) -> list[str]: - user_id = UUID(subject) - result = await self._db.execute( - select(RoleModel.name) - .join(UserHasRoleModel, UserHasRoleModel.role_id == RoleModel.id) - .where(UserHasRoleModel.user_id == user_id) - ) - return list(result.scalars().all()) - - async def create_resource( - self, - resource: AuthorizationResource, - ) -> AuthorizationResource: - model = AuthorizationResourceModel( - id=resource.id, - key=resource.key, - name=resource.name, - description=resource.description, - ) - self._db.add(model) - await self._db.flush() - return self._resource_from_model(model) - - async def list_resources(self) -> list[AuthorizationResource]: - result = await self._db.execute(select(AuthorizationResourceModel)) - return [self._resource_from_model(model) for model in result.scalars().all()] - - async def create_role(self, role: Role) -> Role: - model = RoleModel( - id=role.id, - name=role.name, - description=role.description, - ) - self._db.add(model) - await self._db.flush() - return self._role_from_model(model) - - async def get_role(self, role_id: UUID) -> Role | None: - result = await self._db.execute( - select(RoleModel).where(RoleModel.id == role_id) - ) - model = result.scalar_one_or_none() - if model is None: - return None - return self._role_from_model(model) - - async def list_roles(self) -> list[Role]: - result = await self._db.execute(select(RoleModel)) - return [self._role_from_model(model) for model in result.scalars().all()] - - async def list_roles_cursor( - self, - cursor_created_at: datetime | None = None, - cursor_id: UUID | None = None, - limit: int = 10, - direction: CursorDirection = CursorDirection.DIRECTION_NEXT, - ) -> tuple[list[Role], bool]: - query = select(RoleModel) - query = self._apply_cursor_pagination( - query, - RoleModel, - cursor_created_at, - cursor_id, - direction, - ).limit(limit + 1) - - result = await self._db.execute(query) - models = list(result.scalars().all()) - has_more = len(models) > limit - models = models[:limit] - - if direction == CursorDirection.DIRECTION_PREV: - models = list(reversed(models)) - - return [self._role_from_model(model) for model in models], has_more - - async def update_role(self, role: Role) -> Role | None: - result = await self._db.execute( - select(RoleModel).where(RoleModel.id == role.id) - ) - model = result.scalar_one_or_none() - if model is None: - return None - - old_name = model.name - model.name = role.name - model.description = role.description - await self._db.flush() - - if old_name != role.name: - await self._rename_role_policies(old_name, role.name) - - return self._role_from_model(model) - - async def delete_role(self, role_id: UUID) -> None: - role = await self.get_role(role_id) - if role is None: - return - - await self._db.execute( - delete(RolePermissionModel).where(RolePermissionModel.role_id == role_id) - ) - await self._db.execute( - delete(UserHasRoleModel).where(UserHasRoleModel.role_id == role_id) - ) - await self._db.execute(delete(RoleModel).where(RoleModel.id == role_id)) - await self._db.execute( - delete(CasbinRuleModel).where( - ((CasbinRuleModel.ptype == "p") & (CasbinRuleModel.v0 == role.name)) - | ((CasbinRuleModel.ptype == "g") & (CasbinRuleModel.v1 == role.name)) - ) - ) - await self._db.flush() - - async def create_permission(self, permission: Permission) -> Permission: - resource = await self._get_or_create_resource(permission.resource) - model = PermissionModel( - id=permission.id, - key=permission.key, - resource_id=resource.id, - resource=permission.resource, - action=permission.action, - description=permission.description, - ) - self._db.add(model) - await self._db.flush() - return self._permission_from_model(model) - - async def get_permission(self, permission_id: UUID) -> Permission | None: - result = await self._db.execute( - select(PermissionModel).where(PermissionModel.id == permission_id) - ) - model = result.scalar_one_or_none() - if model is None: - return None - return self._permission_from_model(model) - - async def list_permissions(self) -> list[Permission]: - result = await self._db.execute(select(PermissionModel)) - return [self._permission_from_model(model) for model in result.scalars().all()] - - async def list_permissions_cursor( - self, - cursor_created_at: datetime | None = None, - cursor_id: UUID | None = None, - limit: int = 10, - direction: CursorDirection = CursorDirection.DIRECTION_NEXT, - ) -> tuple[list[Permission], bool]: - query = select(PermissionModel) - query = self._apply_cursor_pagination( - query, - PermissionModel, - cursor_created_at, - cursor_id, - direction, - ).limit(limit + 1) - - result = await self._db.execute(query) - models = list(result.scalars().all()) - has_more = len(models) > limit - models = models[:limit] - - if direction == CursorDirection.DIRECTION_PREV: - models = list(reversed(models)) - - return [self._permission_from_model(model) for model in models], has_more - - async def update_permission(self, permission: Permission) -> Permission | None: - result = await self._db.execute( - select(PermissionModel).where(PermissionModel.id == permission.id) - ) - model = result.scalar_one_or_none() - if model is None: - return None - - old_key = model.key - resource = await self._get_or_create_resource(permission.resource) - model.key = permission.key - model.resource_id = resource.id - model.resource = permission.resource - model.action = permission.action - model.description = permission.description - await self._db.flush() - - if old_key != permission.key: - await self._rename_permission_policies(old_key, permission.key) - - return self._permission_from_model(model) - - async def delete_permission(self, permission_id: UUID) -> None: - permission = await self.get_permission(permission_id) - if permission is None: - return - - await self._db.execute( - delete(RolePermissionModel).where( - RolePermissionModel.permission_id == permission_id - ) - ) - await self._db.execute( - delete(PermissionModel).where(PermissionModel.id == permission_id) - ) - await self._db.execute( - delete(CasbinRuleModel).where( - CasbinRuleModel.ptype == "p", - CasbinRuleModel.v1 == permission.key, - ) - ) - await self._db.flush() - - async def assign_permission_to_role( - self, - role_id: UUID, - permission_id: UUID, - ) -> None: - role = await self.get_role(role_id) - permission = await self.get_permission(permission_id) - if role is None: - raise ValueError("Role does not exist") - if permission is None: - raise ValueError("Permission does not exist") - - existing = await self._db.execute( - select(RolePermissionModel).where( - RolePermissionModel.role_id == role_id, - RolePermissionModel.permission_id == permission_id, - ) - ) - if existing.scalar_one_or_none() is None: - self._db.add( - RolePermissionModel(role_id=role_id, permission_id=permission_id) - ) - await self._db.flush() - - await self.add_policy("p", role.name, permission.key) - - async def list_role_permissions(self) -> list[tuple[str, str]]: - result = await self._db.execute( - select(RoleModel.name, PermissionModel.key) - .join(RolePermissionModel, RolePermissionModel.role_id == RoleModel.id) - .join( - PermissionModel, PermissionModel.id == RolePermissionModel.permission_id - ) - ) - return [ - (role_name, permission_key) for role_name, permission_key in result.all() - ] - - async def remove_permission_from_role( - self, - role_id: UUID, - permission_id: UUID, - ) -> None: - role = await self.get_role(role_id) - permission = await self.get_permission(permission_id) - if role is None or permission is None: - return - - await self._db.execute( - delete(RolePermissionModel).where( - RolePermissionModel.role_id == role_id, - RolePermissionModel.permission_id == permission_id, - ) - ) - await self.remove_policy("p", role.name, permission.key) - - def _to_policy_line(self, rule: CasbinRuleModel) -> str: - values = [rule.v0, rule.v1, rule.v2, rule.v3, rule.v4, rule.v5] - populated = [value for value in values if value is not None] - return ", ".join([rule.ptype, *populated]) - - def _to_policy_tuple(self, rule: CasbinRuleModel) -> tuple[str, ...]: - values = [rule.v0, rule.v1, rule.v2, rule.v3, rule.v4, rule.v5] - populated = [value for value in values if value is not None] - return (rule.ptype, *populated) - - async def _get_or_create_resource( - self, resource_key: str - ) -> AuthorizationResourceModel: - result = await self._db.execute( - select(AuthorizationResourceModel).where( - AuthorizationResourceModel.key == resource_key, - ) - ) - model = result.scalar_one_or_none() - if model is not None: - return model - - model = AuthorizationResourceModel( - key=resource_key, - name=resource_key.replace("_", " ").title(), - description=f"{resource_key} resources", - ) - self._db.add(model) - await self._db.flush() - return model - - def _value_at(self, values: tuple[str, ...], index: int) -> str | None: - if index >= len(values): - return None - return values[index] - - async def _get_role_by_name(self, role: str) -> RoleModel | None: - result = await self._db.execute(select(RoleModel).where(RoleModel.name == role)) - return result.scalar_one_or_none() - - async def _rename_role_policies(self, old_name: str, new_name: str) -> None: - policy_result = await self._db.execute( - select(CasbinRuleModel).where( - CasbinRuleModel.ptype == "p", - CasbinRuleModel.v0 == old_name, - ) - ) - for rule in policy_result.scalars().all(): - rule.v0 = new_name - - grouping_result = await self._db.execute( - select(CasbinRuleModel).where( - CasbinRuleModel.ptype == "g", - CasbinRuleModel.v1 == old_name, - ) - ) - for rule in grouping_result.scalars().all(): - rule.v1 = new_name - - await self._db.flush() - - async def _rename_permission_policies(self, old_key: str, new_key: str) -> None: - result = await self._db.execute( - select(CasbinRuleModel).where( - CasbinRuleModel.ptype == "p", - CasbinRuleModel.v1 == old_key, - ) - ) - for rule in result.scalars().all(): - rule.v1 = new_key - await self._db.flush() - - def _role_from_model(self, model: RoleModel) -> Role: - return Role( - id=model.id, - name=model.name, - description=model.description, - created_at=model.created_at.isoformat(), - updated_at=model.updated_at.isoformat(), - ) - - def _resource_from_model( - self, - model: AuthorizationResourceModel, - ) -> AuthorizationResource: - return AuthorizationResource( - id=model.id, - key=model.key, - name=model.name, - description=model.description, - ) - - def _permission_from_model(self, model: PermissionModel) -> Permission: - return Permission( - id=model.id, - key=model.key, - resource=model.resource, - action=model.action, - description=model.description, - created_at=model.created_at.isoformat(), - updated_at=model.updated_at.isoformat(), - ) - - def _apply_cursor_pagination( - self, - query, - model, - cursor_created_at: datetime | None, - cursor_id: UUID | None, - direction: CursorDirection, - ): - if cursor_created_at and cursor_id: - if direction == CursorDirection.DIRECTION_NEXT: - query = query.where( - or_( - model.created_at < cursor_created_at, - and_( - model.created_at == cursor_created_at, - model.id < cursor_id, - ), - ) - ) - return query.order_by(model.created_at.desc(), model.id.desc()) - - query = query.where( - or_( - model.created_at > cursor_created_at, - and_( - model.created_at == cursor_created_at, - model.id > cursor_id, - ), - ) - ) - return query.order_by(model.created_at.asc(), model.id.asc()) - - return query.order_by(model.created_at.desc(), model.id.desc()) diff --git a/src/modules/authorization/infrastructure/services/__init__.py b/src/modules/authorization/infrastructure/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/modules/authorization/infrastructure/services/casbin_authorization_service.py b/src/modules/authorization/infrastructure/services/casbin_authorization_service.py deleted file mode 100644 index ca52751..0000000 --- a/src/modules/authorization/infrastructure/services/casbin_authorization_service.py +++ /dev/null @@ -1,137 +0,0 @@ -from datetime import datetime -from uuid import UUID - -from src.modules.authorization.domain.permissions import permission_key -from src.modules.authorization.domain.services.authorization_service import ( - AuthorizationService, -) -from src.modules.authorization.infrastructure.repositories.casbin_policy_repository import ( - SQLAlchemyCasbinPolicyRepository, -) -from src.modules.authorization.domain.entities.permission import Permission -from src.modules.authorization.domain.entities.role import Role -from src.shared.utils.cursor import CursorDirection - -CASBIN_MODEL_TEXT = """ -[request_definition] -r = sub, perm - -[policy_definition] -p = sub, perm - -[role_definition] -g = _, _ - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = g(r.sub, p.sub) && (p.perm == "*" || r.perm == p.perm) -""" - - -class CasbinAuthorizationService(AuthorizationService): - def __init__(self, policy_repository: SQLAlchemyCasbinPolicyRepository): - self._policy_repository = policy_repository - - async def can(self, subject: str, resource: str, action: str) -> bool: - enforcer = await self._build_enforcer() - return bool(enforcer.enforce(subject, permission_key(resource, action))) - - async def assign_role(self, subject: str, role: str) -> None: - await self._policy_repository.assign_role(subject, role) - - async def get_roles_for_subject(self, subject: str) -> list[str]: - return await self._policy_repository.get_roles_for_subject(subject) - - async def create_role(self, role: Role) -> Role: - return await self._policy_repository.create_role(role) - - async def update_role(self, role: Role) -> Role | None: - return await self._policy_repository.update_role(role) - - async def delete_role(self, role_id: UUID) -> None: - await self._policy_repository.delete_role(role_id) - - async def get_role(self, role_id: UUID) -> Role | None: - return await self._policy_repository.get_role(role_id) - - async def list_roles(self) -> list[Role]: - return await self._policy_repository.list_roles() - - async def list_roles_cursor( - self, - cursor_created_at: datetime | None = None, - cursor_id: UUID | None = None, - limit: int = 10, - direction: CursorDirection = CursorDirection.DIRECTION_NEXT, - ) -> tuple[list[Role], bool]: - return await self._policy_repository.list_roles_cursor( - cursor_created_at=cursor_created_at, - cursor_id=cursor_id, - limit=limit, - direction=direction, - ) - - async def create_permission(self, permission: Permission) -> Permission: - return await self._policy_repository.create_permission(permission) - - async def update_permission(self, permission: Permission) -> Permission | None: - return await self._policy_repository.update_permission(permission) - - async def delete_permission(self, permission_id: UUID) -> None: - await self._policy_repository.delete_permission(permission_id) - - async def get_permission(self, permission_id: UUID) -> Permission | None: - return await self._policy_repository.get_permission(permission_id) - - async def list_permissions(self) -> list[Permission]: - return await self._policy_repository.list_permissions() - - async def list_permissions_cursor( - self, - cursor_created_at: datetime | None = None, - cursor_id: UUID | None = None, - limit: int = 10, - direction: CursorDirection = CursorDirection.DIRECTION_NEXT, - ) -> tuple[list[Permission], bool]: - return await self._policy_repository.list_permissions_cursor( - cursor_created_at=cursor_created_at, - cursor_id=cursor_id, - limit=limit, - direction=direction, - ) - - async def assign_permission_to_role( - self, - role_id: UUID, - permission_id: UUID, - ) -> None: - await self._policy_repository.assign_permission_to_role(role_id, permission_id) - - async def remove_permission_from_role( - self, - role_id: UUID, - permission_id: UUID, - ) -> None: - await self._policy_repository.remove_permission_from_role( - role_id, - permission_id, - ) - - async def _build_enforcer(self): - try: - import casbin - from casbin import persist - except ModuleNotFoundError as exc: - raise RuntimeError( - "Casbin is not installed. Run `poetry install` to install project dependencies." - ) from exc - - model = casbin.Model() - model.load_model_from_text(CASBIN_MODEL_TEXT) - enforcer = casbin.Enforcer(model) - for policy_line in await self._policy_repository.load_policy_lines(): - persist.load_policy_line(policy_line, enforcer.model) - enforcer.build_role_links() - return enforcer diff --git a/src/modules/authorization/presentation/dependency.py b/src/modules/authorization/presentation/dependency.py index c5f91e0..6e836bf 100644 --- a/src/modules/authorization/presentation/dependency.py +++ b/src/modules/authorization/presentation/dependency.py @@ -8,10 +8,10 @@ from src.modules.authorization.domain.services.authorization_service import ( AuthorizationService, ) -from src.modules.authorization.infrastructure.repositories.casbin_policy_repository import ( +from src.core.authorization.infrastructure.repositories.casbin_policy_repository import ( SQLAlchemyCasbinPolicyRepository, ) -from src.modules.authorization.infrastructure.services.casbin_authorization_service import ( +from src.core.authorization.infrastructure.services.casbin_authorization_service import ( CasbinAuthorizationService, ) diff --git a/src/modules/authorization/presentation/routers/permission_router.py b/src/modules/authorization/presentation/routers/permission_router.py index 04511c7..77fbfb9 100644 --- a/src/modules/authorization/presentation/routers/permission_router.py +++ b/src/modules/authorization/presentation/routers/permission_router.py @@ -19,7 +19,7 @@ UPDATE_ACTION, permission_key, ) -from src.modules.authorization.infrastructure.services.casbin_authorization_service import ( +from src.core.authorization.infrastructure.services.casbin_authorization_service import ( CasbinAuthorizationService, ) from src.modules.authorization.presentation.dependency import ( diff --git a/src/modules/authorization/presentation/routers/role_router.py b/src/modules/authorization/presentation/routers/role_router.py index 928e4b9..6e6efcc 100644 --- a/src/modules/authorization/presentation/routers/role_router.py +++ b/src/modules/authorization/presentation/routers/role_router.py @@ -18,7 +18,7 @@ ROLE_RESOURCE, UPDATE_ACTION, ) -from src.modules.authorization.infrastructure.services.casbin_authorization_service import ( +from src.core.authorization.infrastructure.services.casbin_authorization_service import ( CasbinAuthorizationService, ) from src.modules.authorization.presentation.dependency import (