Quellcode durchsuchen

refactor(moderator-service): move to dedicated module, add new exceptions and Permission-objects, adapt service to work with them

Librellium vor 3 Wochen
Ursprung
Commit
f114de9196

+ 0 - 1
anonflow/services/accounts/exceptions.py

@@ -1 +0,0 @@
-class ForbiddenError(PermissionError): ...

+ 0 - 132
anonflow/services/accounts/moderator.py

@@ -1,132 +0,0 @@
-import logging
-from typing import Optional
-
-from sqlalchemy.exc import IntegrityError
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from anonflow.database import BanRepository, Database, ModeratorRepository
-
-from .exceptions import ForbiddenError
-
-
-class ModeratorService:
-    def __init__(
-        self,
-        database: Database,
-        ban_repository: BanRepository,
-        moderator_repository: ModeratorRepository
-    ):
-        self._logger = logging.getLogger(__name__)
-
-        self._database = database
-        self._ban_repository = ban_repository
-        self._moderator_repository = moderator_repository
-
-    async def add(self, actor_user_id: int, user_id: int):
-        try:
-            async with self._database.get_session() as session:
-                if await self._can_manage_moderators(session, actor_user_id):
-                    await self._moderator_repository.add(session, user_id)
-                else:
-                    raise ForbiddenError()
-        except IntegrityError:
-            self._logger.warning("Failed to add moderator user_id=%s", user_id)
-
-    async def ban(self, actor_user_id: int, user_id: int):
-        async with self._database.get_session() as session:
-            if await self._can_manage_bans(session, actor_user_id):
-                await self._ban_repository.ban(session, actor_user_id, user_id)
-            else:
-                raise ForbiddenError()
-
-    async def _get_permission(self, session: AsyncSession, actor_user_id: int, name: str) -> bool:
-        moderator = await self._moderator_repository.get(session, actor_user_id)
-        return getattr(getattr(moderator, name, None), "value", False)
-
-    async def _can_approve_posts(self, session: AsyncSession, actor_user_id: int):
-        return await self._get_permission(session, actor_user_id, "can_approve_posts")
-
-    async def can_approve_posts(self, actor_user_id: int):
-        async with self._database.get_session() as session:
-            return await self._can_approve_posts(session, actor_user_id)
-
-    async def _can_manage_bans(self, session: AsyncSession, actor_user_id: int):
-        return await self._get_permission(session, actor_user_id, "can_manage_bans")
-
-    async def can_manage_bans(self, actor_user_id: int):
-        async with self._database.get_session() as session:
-            return await self._can_manage_bans(session, actor_user_id)
-
-    async def _can_manage_moderators(self, session: AsyncSession, actor_user_id: int):
-        return await self._get_permission(session, actor_user_id, "can_manage_moderators")
-
-    async def can_manage_moderators(self, actor_user_id: int):
-        async with self._database.get_session() as session:
-            return await self._can_manage_moderators(session, actor_user_id)
-
-    async def get(self, user_id: int):
-        async with self._database.get_session() as session:
-            return await self._moderator_repository.get(session, user_id)
-
-    async def get_permissions(self, user_id: int):
-        async with self._database.get_session() as session:
-            return await self._moderator_repository.get_permissions(session, user_id)
-
-    async def has(self, user_id: int):
-        async with self._database.get_session() as session:
-            return await self._moderator_repository.has(session, user_id)
-
-    async def is_banned(self, user_id: int):
-        async with self._database.get_session() as session:
-            return await self._ban_repository.is_banned(session, user_id)
-
-    async def remove(self, actor_user_id: int, user_id: int):
-        try:
-            async with self._database.get_session() as session:
-                if await self._can_manage_moderators(session, actor_user_id):
-                    await self._moderator_repository.remove(session, user_id)
-                else:
-                    raise ForbiddenError()
-        except IntegrityError:
-            self._logger.warning("Failed to remove moderator user_id=%s", user_id)
-
-    async def unban(self, actor_user_id: int, user_id: int):
-        async with self._database.get_session() as session:
-            if await self._can_manage_bans(session, actor_user_id):
-                await self._ban_repository.unban(session, actor_user_id, user_id)
-            else:
-                raise ForbiddenError()
-
-    async def update(self, actor_user_id: int, user_id: int, **fields):
-        try:
-            async with self._database.get_session() as session:
-                if await self._can_manage_moderators(session, actor_user_id):
-                    await self._moderator_repository.update(session, user_id, **fields)
-                else:
-                    raise ForbiddenError()
-        except IntegrityError:
-            self._logger.warning("Failed to update moderator user_id=%s", user_id)
-
-    async def update_permissions(
-        self,
-        actor_user_id: int,
-        user_id: int,
-        *,
-        can_approve_posts: Optional[bool] = None,
-        can_manage_bans: Optional[bool] = None,
-        can_manage_moderators: Optional[bool] = None
-    ):
-        try:
-            async with self._database.get_session() as session:
-                if await self._can_manage_moderators(session, actor_user_id):
-                    await self._moderator_repository.update_permissions(
-                        session,
-                        user_id,
-                        can_approve_posts=can_approve_posts,
-                        can_manage_bans=can_manage_bans,
-                        can_manage_moderators=can_manage_moderators
-                    )
-                else:
-                    raise ForbiddenError()
-        except IntegrityError:
-            self._logger.warning("Failed to update moderator user_id=%s", user_id)

+ 3 - 0
anonflow/services/accounts/moderator/__init__.py

@@ -0,0 +1,3 @@
+from .service import ModeratorService
+
+__all__ = ["ModeratorService"]

+ 3 - 0
anonflow/services/accounts/moderator/exceptions.py

@@ -0,0 +1,3 @@
+class ModeratorPermissionError(PermissionError): ...
+
+class SelfActionError(ModeratorPermissionError): ...

+ 17 - 0
anonflow/services/accounts/moderator/permissions.py

@@ -0,0 +1,17 @@
+from dataclasses import dataclass, asdict
+from enum import Enum
+
+
+@dataclass(frozen=True)
+class ModeratorPermissions:
+    can_approve_posts: bool = False
+    can_manage_bans: bool = False
+    can_manage_moderators: bool = False
+
+    def to_dict(self):
+        return asdict(self)
+
+class ModeratorPermission(str, Enum):
+    APPROVE_POSTS = "can_approve_posts"
+    MANAGE_BANS = "can_manage_bans"
+    MANAGE_MODERATORS = "can_manage_moderators"

+ 156 - 0
anonflow/services/accounts/moderator/service.py

@@ -0,0 +1,156 @@
+import logging
+
+from sqlalchemy.exc import IntegrityError
+from sqlalchemy.ext.asyncio import AsyncSession
+
+from anonflow.constants import SYSTEM_USER_ID
+from anonflow.database import BanRepository, Database, ModeratorRepository
+
+from .exceptions import ModeratorPermissionError, SelfActionError
+from .permissions import ModeratorPermission, ModeratorPermissions
+
+
+class ModeratorService:
+    def __init__(
+        self,
+        database: Database,
+        ban_repository: BanRepository,
+        moderator_repository: ModeratorRepository
+    ):
+        self._logger = logging.getLogger(__name__)
+
+        self._database = database
+        self._ban_repository = ban_repository
+        self._moderator_repository = moderator_repository
+
+    @staticmethod
+    def _assert_not_self(actor_user_id: int, user_id: int):
+        if actor_user_id == user_id:
+            raise SelfActionError(
+                f"Moderator user_id={actor_user_id} cannot perform this action on themselves (target user_id={user_id})."
+            )
+
+    async def add(self, actor_user_id: int, user_id: int):
+        try:
+            async with self._database.begin_session() as session:
+                if await self._can(session, actor_user_id, ModeratorPermission.MANAGE_MODERATORS):
+                    self._assert_not_self(actor_user_id, user_id)
+                    await self._moderator_repository.add(session, user_id)
+                else:
+                    raise ModeratorPermissionError(
+                        f"Moderator user_id={actor_user_id} does not have permission to perform 'add'."
+                    )
+        except IntegrityError:
+            self._logger.warning("Failed to add moderator user_id=%s", user_id)
+
+    async def ban(self, actor_user_id: int, user_id: int):
+        async with self._database.begin_session() as session:
+            if await self._can(session, actor_user_id, ModeratorPermission.MANAGE_BANS):
+                self._assert_not_self(actor_user_id, user_id)
+                await self._ban_repository.ban(session, actor_user_id, user_id)
+            else:
+                raise ModeratorPermissionError(
+                    f"Moderator user_id={actor_user_id} does not have permission to perform 'ban'."
+                )
+
+    async def _can(self, session: AsyncSession, actor_user_id: int, permission: ModeratorPermission) -> bool:
+        moderator = await self._moderator_repository.get(session, actor_user_id)
+        if moderator:
+            if moderator.is_root.value:
+                return True
+            return getattr(getattr(moderator, permission, None), "value", False)
+
+        return False
+
+    async def can(self, actor_user_id: int, permission: ModeratorPermission):
+        async with self._database.get_session() as session:
+            return self._can(session, actor_user_id, permission)
+
+    async def get(self, user_id: int):
+        async with self._database.get_session() as session:
+            return await self._moderator_repository.get(session, user_id)
+
+    async def get_permissions(self, user_id: int):
+        async with self._database.get_session() as session:
+            result = await self._moderator_repository.get(session, user_id)
+            if not result:
+                return ModeratorPermissions()
+
+            return ModeratorPermissions(
+                **{
+                    key: value
+                    for key, value in result.__dict__.items()
+                    if key.startswith("can_")
+                }
+            )
+
+    async def has(self, user_id: int):
+        async with self._database.get_session() as session:
+            return await self._moderator_repository.has(session, user_id)
+
+    async def init(self):
+        async with self._database.begin_session() as session:
+            if not await self._moderator_repository.has(session, SYSTEM_USER_ID):
+                await self._moderator_repository.add(session, SYSTEM_USER_ID, is_root=True)
+
+    async def is_banned(self, user_id: int):
+        async with self._database.get_session() as session:
+            return await self._ban_repository.is_banned(session, user_id)
+
+    async def remove(self, actor_user_id: int, user_id: int):
+        try:
+            async with self._database.begin_session() as session:
+                if await self._can(session, actor_user_id, ModeratorPermission.MANAGE_MODERATORS):
+                    self._assert_not_self(actor_user_id, user_id)
+                    await self._moderator_repository.remove(session, user_id)
+                else:
+                    raise ModeratorPermissionError(
+                        f"Moderator user_id={actor_user_id} does not have permission to perform 'remove'."
+                    )
+        except IntegrityError:
+            self._logger.warning("Failed to remove moderator user_id=%s", user_id)
+
+    async def unban(self, actor_user_id: int, user_id: int):
+        async with self._database.begin_session() as session:
+            if await self._can(session, actor_user_id, ModeratorPermission.MANAGE_BANS):
+                self._assert_not_self(actor_user_id, user_id)
+                await self._ban_repository.unban(session, actor_user_id, user_id)
+            else:
+                raise ModeratorPermissionError(
+                    f"Moderator user_id={actor_user_id} does not have permission to perform 'unban'."
+                )
+
+    async def update(self, actor_user_id: int, user_id: int, **fields):
+        try:
+            async with self._database.begin_session() as session:
+                if await self._can(session, actor_user_id, ModeratorPermission.MANAGE_MODERATORS):
+                    self._assert_not_self(actor_user_id, user_id)
+                    await self._moderator_repository.update(session, user_id, **fields)
+                else:
+                    raise ModeratorPermissionError(
+                        f"Moderator user_id={actor_user_id} does not have permission to perform 'update'."
+                    )
+        except IntegrityError:
+            self._logger.warning("Failed to update moderator user_id=%s", user_id)
+
+    async def update_permissions(
+        self,
+        actor_user_id: int,
+        user_id: int,
+        permissions: ModeratorPermissions
+    ):
+        try:
+            async with self._database.begin_session() as session:
+                if await self._can(session, actor_user_id, ModeratorPermission.MANAGE_MODERATORS):
+                    self._assert_not_self(actor_user_id, user_id)
+                    await self._moderator_repository.update(
+                        session,
+                        user_id,
+                        **permissions.to_dict()
+                    )
+                else:
+                    raise ModeratorPermissionError(
+                        f"Moderator user_id={actor_user_id} does not have permission to perform 'update_permissions'."
+                    )
+        except IntegrityError:
+            self._logger.warning("Failed to update moderator user_id=%s", user_id)