Implement server-authoritative roles and Shift+Z admin flows
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ class ClientConnection:
|
||||
user_id: str | None = None
|
||||
username: str | None = None
|
||||
role: str = "user"
|
||||
permissions: set[str] | None = None
|
||||
session_token: str | None = None
|
||||
nickname: str = "user..."
|
||||
saved_x: int | None = None
|
||||
|
||||
@@ -63,6 +63,47 @@ class AuthLogoutPacket(BasePacket):
|
||||
type: Literal["auth_logout"]
|
||||
|
||||
|
||||
class AdminRolesListPacket(BasePacket):
|
||||
type: Literal["admin_roles_list"]
|
||||
|
||||
|
||||
class AdminRoleCreatePacket(BasePacket):
|
||||
type: Literal["admin_role_create"]
|
||||
name: str = Field(min_length=1, max_length=32)
|
||||
|
||||
|
||||
class AdminRoleUpdatePermissionsPacket(BasePacket):
|
||||
type: Literal["admin_role_update_permissions"]
|
||||
role: str = Field(min_length=1, max_length=32)
|
||||
permissions: list[str]
|
||||
|
||||
|
||||
class AdminRoleDeletePacket(BasePacket):
|
||||
type: Literal["admin_role_delete"]
|
||||
role: str = Field(min_length=1, max_length=32)
|
||||
replacementRole: str = Field(min_length=1, max_length=32)
|
||||
|
||||
|
||||
class AdminUsersListPacket(BasePacket):
|
||||
type: Literal["admin_users_list"]
|
||||
|
||||
|
||||
class AdminUserSetRolePacket(BasePacket):
|
||||
type: Literal["admin_user_set_role"]
|
||||
username: str = Field(min_length=1, max_length=128)
|
||||
role: str = Field(min_length=1, max_length=32)
|
||||
|
||||
|
||||
class AdminUserBanPacket(BasePacket):
|
||||
type: Literal["admin_user_ban"]
|
||||
username: str = Field(min_length=1, max_length=128)
|
||||
|
||||
|
||||
class AdminUserUnbanPacket(BasePacket):
|
||||
type: Literal["admin_user_unban"]
|
||||
username: str = Field(min_length=1, max_length=128)
|
||||
|
||||
|
||||
class PingPacket(BasePacket):
|
||||
type: Literal["ping"]
|
||||
clientSentAt: int
|
||||
@@ -131,6 +172,14 @@ ClientPacket = (
|
||||
| AuthLoginPacket
|
||||
| AuthResumePacket
|
||||
| AuthLogoutPacket
|
||||
| AdminRolesListPacket
|
||||
| AdminRoleCreatePacket
|
||||
| AdminRoleUpdatePermissionsPacket
|
||||
| AdminRoleDeletePacket
|
||||
| AdminUsersListPacket
|
||||
| AdminUserSetRolePacket
|
||||
| AdminUserBanPacket
|
||||
| AdminUserUnbanPacket
|
||||
| PingPacket
|
||||
| ItemAddPacket
|
||||
| ItemPickupPacket
|
||||
@@ -176,10 +225,17 @@ class AuthResultPacket(BasePacket):
|
||||
sessionToken: str | None = None
|
||||
username: str | None = None
|
||||
role: str | None = None
|
||||
permissions: list[str] | None = None
|
||||
nickname: str | None = None
|
||||
authPolicy: dict | None = None
|
||||
|
||||
|
||||
class AuthPermissionsPacket(BasePacket):
|
||||
type: Literal["auth_permissions"]
|
||||
role: str
|
||||
permissions: list[str]
|
||||
|
||||
|
||||
class UserLeftPacket(BasePacket):
|
||||
type: Literal["user_left"]
|
||||
id: str
|
||||
@@ -343,3 +399,43 @@ class ItemPianoStatusPacket(BasePacket):
|
||||
"playback_stopped",
|
||||
]
|
||||
recordingState: Literal["idle", "recording", "paused", "playback"] | None = None
|
||||
|
||||
|
||||
class AdminRoleSummary(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
isSystem: bool
|
||||
userCount: int
|
||||
permissions: list[str]
|
||||
|
||||
|
||||
class AdminRolesListResultPacket(BasePacket):
|
||||
type: Literal["admin_roles_list"]
|
||||
roles: list[AdminRoleSummary]
|
||||
permissionKeys: list[str]
|
||||
|
||||
|
||||
class AdminUserSummary(BaseModel):
|
||||
id: str
|
||||
username: str
|
||||
role: str
|
||||
status: Literal["active", "disabled"]
|
||||
|
||||
|
||||
class AdminUsersListResultPacket(BasePacket):
|
||||
type: Literal["admin_users_list"]
|
||||
users: list[AdminUserSummary]
|
||||
|
||||
|
||||
class AdminActionResultPacket(BasePacket):
|
||||
type: Literal["admin_action_result"]
|
||||
ok: bool
|
||||
action: Literal[
|
||||
"role_create",
|
||||
"role_update_permissions",
|
||||
"role_delete",
|
||||
"user_set_role",
|
||||
"user_ban",
|
||||
"user_unban",
|
||||
]
|
||||
message: str
|
||||
|
||||
@@ -48,10 +48,22 @@ from .items.types.clock.time_format import parse_alarm_time_flexible
|
||||
from .models import (
|
||||
AuthLoginPacket,
|
||||
AuthLogoutPacket,
|
||||
AuthPermissionsPacket,
|
||||
AuthRegisterPacket,
|
||||
AuthRequiredPacket,
|
||||
AuthResultPacket,
|
||||
AuthResumePacket,
|
||||
AdminActionResultPacket,
|
||||
AdminRoleCreatePacket,
|
||||
AdminRoleDeletePacket,
|
||||
AdminRoleUpdatePermissionsPacket,
|
||||
AdminRolesListPacket,
|
||||
AdminRolesListResultPacket,
|
||||
AdminUserBanPacket,
|
||||
AdminUserSetRolePacket,
|
||||
AdminUserUnbanPacket,
|
||||
AdminUsersListPacket,
|
||||
AdminUsersListResultPacket,
|
||||
BroadcastChatMessagePacket,
|
||||
BroadcastNicknamePacket,
|
||||
BroadcastPositionPacket,
|
||||
@@ -225,6 +237,61 @@ class SignalingServer:
|
||||
"passwordMaxLength": self.auth_service.password_max_length,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _sorted_permissions(values: set[str] | tuple[str, ...] | None) -> list[str]:
|
||||
"""Return deterministic sorted permission list."""
|
||||
|
||||
if not values:
|
||||
return []
|
||||
return sorted(str(value) for value in values if str(value).strip())
|
||||
|
||||
def _client_has_permission(self, client: ClientConnection, key: str) -> bool:
|
||||
"""Return whether one authenticated client currently has a permission key."""
|
||||
|
||||
if not client.authenticated or not client.user_id:
|
||||
return False
|
||||
if client.permissions is None:
|
||||
client.permissions = self.auth_service.get_user_permissions(client.user_id)
|
||||
return key in client.permissions
|
||||
|
||||
def _refresh_client_permissions(self, client: ClientConnection) -> list[str]:
|
||||
"""Refresh one client's role/permissions from auth storage and return permissions list."""
|
||||
|
||||
if not client.user_id:
|
||||
client.permissions = set()
|
||||
return []
|
||||
user = self.auth_service.get_user_by_id(client.user_id)
|
||||
if user is None:
|
||||
client.permissions = set()
|
||||
return []
|
||||
client.role = user.role
|
||||
client.permissions = set(user.permissions)
|
||||
return self._sorted_permissions(client.permissions)
|
||||
|
||||
async def _send_auth_permissions(self, client: ClientConnection) -> None:
|
||||
"""Push one authenticated client's current role + permission set."""
|
||||
|
||||
permissions = self._refresh_client_permissions(client)
|
||||
await self._send(
|
||||
client.websocket,
|
||||
AuthPermissionsPacket(
|
||||
type="auth_permissions",
|
||||
role=client.role,
|
||||
permissions=permissions,
|
||||
),
|
||||
)
|
||||
|
||||
async def _sync_permissions_for_user_ids(self, user_ids: list[str]) -> None:
|
||||
"""Refresh and push permissions for active websocket clients matching user ids."""
|
||||
|
||||
wanted = {str(user_id) for user_id in user_ids}
|
||||
if not wanted:
|
||||
return
|
||||
for active in self.clients.values():
|
||||
if not active.user_id or active.user_id not in wanted:
|
||||
continue
|
||||
await self._send_auth_permissions(active)
|
||||
|
||||
def _flush_state_save(self) -> None:
|
||||
"""Immediately flush pending state persistence and clear debounce state."""
|
||||
|
||||
@@ -412,6 +479,14 @@ class SignalingServer:
|
||||
actor_name = client.username or client.nickname or actor_id
|
||||
return actor_id, actor_name
|
||||
|
||||
@staticmethod
|
||||
def _owns_item(client: ClientConnection, item: WorldItem) -> bool:
|
||||
"""Return whether the authenticated client is the creator/owner of an item."""
|
||||
|
||||
if not client.user_id:
|
||||
return False
|
||||
return item.createdBy == client.user_id
|
||||
|
||||
def _get_item_emit_range(self, item: WorldItem) -> int:
|
||||
"""Return effective emit range for one item with sane bounds."""
|
||||
|
||||
@@ -1211,6 +1286,7 @@ class SignalingServer:
|
||||
"userId": client.user_id,
|
||||
"username": client.username,
|
||||
"role": client.role if client.authenticated else None,
|
||||
"permissions": self._sorted_permissions(client.permissions),
|
||||
"policy": self._auth_policy(),
|
||||
},
|
||||
)
|
||||
@@ -1230,6 +1306,7 @@ class SignalingServer:
|
||||
client.x = random.randrange(self.grid_size)
|
||||
client.y = random.randrange(self.grid_size)
|
||||
now_ms = self.item_service.now_ms()
|
||||
self._refresh_client_permissions(client)
|
||||
client.last_position_update_ms = now_ms
|
||||
client.movement_window_index = self._movement_window_index(now_ms)
|
||||
client.movement_window_steps_used = 0
|
||||
@@ -1324,6 +1401,7 @@ class SignalingServer:
|
||||
if client.session_token:
|
||||
self.auth_service.revoke(client.session_token)
|
||||
client.session_token = None
|
||||
client.permissions = set()
|
||||
LOGGER.info("auth logout id=%s ip=%s username=%s", client.id, self._client_ip(client), client.username)
|
||||
await self._send(
|
||||
client.websocket,
|
||||
@@ -1387,6 +1465,7 @@ class SignalingServer:
|
||||
client.user_id = session.user.id
|
||||
client.username = session.user.username
|
||||
client.role = session.user.role
|
||||
client.permissions = set(session.user.permissions)
|
||||
client.session_token = session.token
|
||||
client.nickname = session.user.last_nickname or client.nickname
|
||||
client.saved_x = session.user.last_x
|
||||
@@ -1400,6 +1479,7 @@ class SignalingServer:
|
||||
sessionToken=session.token,
|
||||
username=session.user.username,
|
||||
role=session.user.role,
|
||||
permissions=self._sorted_permissions(session.user.permissions),
|
||||
nickname=client.nickname,
|
||||
authPolicy=self._auth_policy(),
|
||||
),
|
||||
@@ -1449,6 +1529,220 @@ class SignalingServer:
|
||||
BroadcastChatMessagePacket(type="chat_message", message=self_message, system=True),
|
||||
)
|
||||
|
||||
async def _send_admin_action_result(
|
||||
self,
|
||||
client: ClientConnection,
|
||||
*,
|
||||
ok: bool,
|
||||
action: Literal[
|
||||
"role_create",
|
||||
"role_update_permissions",
|
||||
"role_delete",
|
||||
"user_set_role",
|
||||
"user_ban",
|
||||
"user_unban",
|
||||
],
|
||||
message: str,
|
||||
) -> None:
|
||||
"""Send one structured admin action result packet to caller."""
|
||||
|
||||
await self._send(
|
||||
client.websocket,
|
||||
AdminActionResultPacket(type="admin_action_result", ok=ok, action=action, message=message),
|
||||
)
|
||||
|
||||
async def _handle_admin_packet(self, client: ClientConnection, packet: ClientPacket) -> bool:
|
||||
"""Handle role/user administration packets with permission checks."""
|
||||
|
||||
if not isinstance(
|
||||
packet,
|
||||
(
|
||||
AdminRolesListPacket,
|
||||
AdminRoleCreatePacket,
|
||||
AdminRoleUpdatePermissionsPacket,
|
||||
AdminRoleDeletePacket,
|
||||
AdminUsersListPacket,
|
||||
AdminUserSetRolePacket,
|
||||
AdminUserBanPacket,
|
||||
AdminUserUnbanPacket,
|
||||
),
|
||||
):
|
||||
return False
|
||||
|
||||
async def deny(action: str, message: str) -> None:
|
||||
await self._send_admin_action_result(client, ok=False, action=action, message=message)
|
||||
|
||||
if isinstance(packet, AdminRolesListPacket):
|
||||
if not (
|
||||
self._client_has_permission(client, "role.manage")
|
||||
or self._client_has_permission(client, "user.change_role")
|
||||
):
|
||||
await deny("role_update_permissions", "Not authorized.")
|
||||
return True
|
||||
roles = self.auth_service.list_roles_with_counts()
|
||||
await self._send(
|
||||
client.websocket,
|
||||
AdminRolesListResultPacket(
|
||||
type="admin_roles_list",
|
||||
roles=roles,
|
||||
permissionKeys=self.auth_service.list_all_permissions(),
|
||||
),
|
||||
)
|
||||
return True
|
||||
|
||||
if isinstance(packet, AdminUsersListPacket):
|
||||
if not (
|
||||
self._client_has_permission(client, "user.change_role")
|
||||
or self._client_has_permission(client, "user.ban_unban")
|
||||
):
|
||||
await deny("user_set_role", "Not authorized.")
|
||||
return True
|
||||
users = self.auth_service.list_users_for_admin()
|
||||
await self._send(client.websocket, AdminUsersListResultPacket(type="admin_users_list", users=users))
|
||||
return True
|
||||
|
||||
if isinstance(packet, AdminRoleCreatePacket):
|
||||
if not self._client_has_permission(client, "role.manage"):
|
||||
await deny("role_create", "Not authorized.")
|
||||
return True
|
||||
try:
|
||||
created = self.auth_service.create_role(packet.name)
|
||||
except AuthError as exc:
|
||||
await deny("role_create", str(exc))
|
||||
return True
|
||||
LOGGER.info("role created actor=%s role=%s", client.user_id, created["name"])
|
||||
await self._send_admin_action_result(client, ok=True, action="role_create", message=f"Created role {created['name']}.")
|
||||
return True
|
||||
|
||||
if isinstance(packet, AdminRoleUpdatePermissionsPacket):
|
||||
if not self._client_has_permission(client, "role.manage"):
|
||||
await deny("role_update_permissions", "Not authorized.")
|
||||
return True
|
||||
affected_user_ids = self.auth_service.list_connected_user_ids_for_role(packet.role)
|
||||
try:
|
||||
assigned = self.auth_service.update_role_permissions(packet.role, packet.permissions)
|
||||
except AuthError as exc:
|
||||
await deny("role_update_permissions", str(exc))
|
||||
return True
|
||||
LOGGER.info(
|
||||
"role permissions updated actor=%s role=%s permission_count=%d",
|
||||
client.user_id,
|
||||
packet.role,
|
||||
len(assigned),
|
||||
)
|
||||
await self._sync_permissions_for_user_ids(affected_user_ids)
|
||||
await self._send_admin_action_result(
|
||||
client,
|
||||
ok=True,
|
||||
action="role_update_permissions",
|
||||
message=f"Updated permissions for {packet.role}.",
|
||||
)
|
||||
return True
|
||||
|
||||
if isinstance(packet, AdminRoleDeletePacket):
|
||||
if not self._client_has_permission(client, "role.manage"):
|
||||
await deny("role_delete", "Not authorized.")
|
||||
return True
|
||||
try:
|
||||
affected_usernames, replacement = self.auth_service.delete_role(packet.role, packet.replacementRole)
|
||||
except AuthError as exc:
|
||||
await deny("role_delete", str(exc))
|
||||
return True
|
||||
affected_ids = [
|
||||
user_id
|
||||
for username in affected_usernames
|
||||
for user_id in [self.auth_service.get_user_id_by_username(username)]
|
||||
if user_id is not None
|
||||
]
|
||||
await self._sync_permissions_for_user_ids(affected_ids)
|
||||
LOGGER.info(
|
||||
"role deleted actor=%s role=%s replacement=%s affected=%d",
|
||||
client.user_id,
|
||||
packet.role,
|
||||
replacement,
|
||||
len(affected_usernames),
|
||||
)
|
||||
await self._send_admin_action_result(
|
||||
client,
|
||||
ok=True,
|
||||
action="role_delete",
|
||||
message=f"Deleted role {packet.role}; reassigned {len(affected_usernames)} users to {replacement}.",
|
||||
)
|
||||
return True
|
||||
|
||||
if isinstance(packet, AdminUserSetRolePacket):
|
||||
if not self._client_has_permission(client, "user.change_role"):
|
||||
await deny("user_set_role", "Not authorized.")
|
||||
return True
|
||||
target_id = self.auth_service.get_user_id_by_username(packet.username)
|
||||
try:
|
||||
username = self.auth_service.set_user_role(packet.username, packet.role, actor_user_id=client.user_id)
|
||||
except AuthError as exc:
|
||||
await deny("user_set_role", str(exc))
|
||||
return True
|
||||
if target_id:
|
||||
await self._sync_permissions_for_user_ids([target_id])
|
||||
LOGGER.info("user role changed actor=%s target=%s role=%s", client.user_id, username, packet.role)
|
||||
await self._send_admin_action_result(
|
||||
client,
|
||||
ok=True,
|
||||
action="user_set_role",
|
||||
message=f"Set role for {username} to {packet.role}.",
|
||||
)
|
||||
return True
|
||||
|
||||
if isinstance(packet, AdminUserBanPacket):
|
||||
if not self._client_has_permission(client, "user.ban_unban"):
|
||||
await deny("user_ban", "Not authorized.")
|
||||
return True
|
||||
target_id = self.auth_service.get_user_id_by_username(packet.username)
|
||||
try:
|
||||
username = self.auth_service.set_user_status(packet.username, "disabled")
|
||||
except AuthError as exc:
|
||||
await deny("user_ban", str(exc))
|
||||
return True
|
||||
if target_id:
|
||||
await self._sync_permissions_for_user_ids([target_id])
|
||||
for active in list(self.clients.values()):
|
||||
if active.user_id != target_id:
|
||||
continue
|
||||
await self._send(
|
||||
active.websocket,
|
||||
AuthResultPacket(type="auth_result", ok=False, message="Account is disabled."),
|
||||
)
|
||||
await active.websocket.close()
|
||||
LOGGER.info("user banned actor=%s target=%s", client.user_id, username)
|
||||
await self._send_admin_action_result(
|
||||
client,
|
||||
ok=True,
|
||||
action="user_ban",
|
||||
message=f"Banned {username}.",
|
||||
)
|
||||
return True
|
||||
|
||||
if isinstance(packet, AdminUserUnbanPacket):
|
||||
if not self._client_has_permission(client, "user.ban_unban"):
|
||||
await deny("user_unban", "Not authorized.")
|
||||
return True
|
||||
target_id = self.auth_service.get_user_id_by_username(packet.username)
|
||||
try:
|
||||
username = self.auth_service.set_user_status(packet.username, "active")
|
||||
except AuthError as exc:
|
||||
await deny("user_unban", str(exc))
|
||||
return True
|
||||
if target_id:
|
||||
await self._sync_permissions_for_user_ids([target_id])
|
||||
LOGGER.info("user unbanned actor=%s target=%s", client.user_id, username)
|
||||
await self._send_admin_action_result(
|
||||
client,
|
||||
ok=True,
|
||||
action="user_unban",
|
||||
message=f"Unbanned {username}.",
|
||||
)
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
async def _handle_message(self, client: ClientConnection, raw_message: str) -> None:
|
||||
"""Decode, validate, and route one inbound client packet."""
|
||||
|
||||
@@ -1470,6 +1764,8 @@ class SignalingServer:
|
||||
client.authenticated = True
|
||||
client.user_id = client.user_id or client.id
|
||||
client.username = client.username or client.nickname
|
||||
client.role = "admin"
|
||||
client.permissions = set(self.auth_service.list_all_permissions())
|
||||
|
||||
if await self._handle_auth_packet(client, packet):
|
||||
return
|
||||
@@ -1480,6 +1776,9 @@ class SignalingServer:
|
||||
)
|
||||
return
|
||||
|
||||
if await self._handle_admin_packet(client, packet):
|
||||
return
|
||||
|
||||
if isinstance(packet, UpdatePositionPacket):
|
||||
if not self._is_in_bounds(packet.x, packet.y):
|
||||
PACKET_LOGGER.warning(
|
||||
@@ -1585,6 +1884,18 @@ class SignalingServer:
|
||||
return
|
||||
|
||||
if isinstance(packet, UpdateNicknamePacket):
|
||||
if not self._client_has_permission(client, "profile.update_nickname"):
|
||||
await self._send(
|
||||
client.websocket,
|
||||
NicknameResultPacket(
|
||||
type="nickname_result",
|
||||
accepted=False,
|
||||
requestedNickname=packet.nickname,
|
||||
effectiveNickname=client.nickname,
|
||||
reason="Not authorized to change nickname.",
|
||||
),
|
||||
)
|
||||
return
|
||||
requested_nickname = packet.nickname.strip()
|
||||
if not requested_nickname:
|
||||
await self._send(
|
||||
@@ -1676,6 +1987,16 @@ class SignalingServer:
|
||||
return
|
||||
|
||||
if isinstance(packet, ChatMessagePacket):
|
||||
if not self._client_has_permission(client, "chat.send"):
|
||||
await self._send(
|
||||
client.websocket,
|
||||
BroadcastChatMessagePacket(
|
||||
type="chat_message",
|
||||
message="You are not allowed to send chat messages.",
|
||||
system=True,
|
||||
),
|
||||
)
|
||||
return
|
||||
await self._broadcast(
|
||||
BroadcastChatMessagePacket(
|
||||
type="chat_message",
|
||||
@@ -1695,6 +2016,9 @@ class SignalingServer:
|
||||
return
|
||||
|
||||
if isinstance(packet, ItemAddPacket):
|
||||
if not self._client_has_permission(client, "item.create"):
|
||||
await self._send_item_result(client, False, "add", "Not authorized to create items.")
|
||||
return
|
||||
if not is_known_item_type(packet.itemType):
|
||||
await self._send_item_result(client, False, "add", "Unknown item type.")
|
||||
return
|
||||
@@ -1744,6 +2068,11 @@ class SignalingServer:
|
||||
if item.carrierId is None and (item.x != client.x or item.y != client.y):
|
||||
await self._send_item_result(client, False, "pickup", "Item is not on your square.", item.id)
|
||||
return
|
||||
can_pickup_any = self._client_has_permission(client, "item.pickup_drop.any")
|
||||
can_pickup_own = self._client_has_permission(client, "item.pickup_drop.own") and self._owns_item(client, item)
|
||||
if not can_pickup_any and not can_pickup_own:
|
||||
await self._send_item_result(client, False, "pickup", "Not authorized to pick up this item.", item.id)
|
||||
return
|
||||
item.carrierId = client.id
|
||||
item.x = client.x
|
||||
item.y = client.y
|
||||
@@ -1776,6 +2105,11 @@ class SignalingServer:
|
||||
if not self._is_in_bounds(packet.x, packet.y):
|
||||
await self._send_item_result(client, False, "drop", "Drop position is out of bounds.", item.id)
|
||||
return
|
||||
can_drop_any = self._client_has_permission(client, "item.pickup_drop.any")
|
||||
can_drop_own = self._client_has_permission(client, "item.pickup_drop.own") and self._owns_item(client, item)
|
||||
if not can_drop_any and not can_drop_own:
|
||||
await self._send_item_result(client, False, "drop", "Not authorized to drop this item.", item.id)
|
||||
return
|
||||
item.carrierId = None
|
||||
item.x = packet.x
|
||||
item.y = packet.y
|
||||
@@ -1808,6 +2142,11 @@ class SignalingServer:
|
||||
if item.carrierId is None and (item.x != client.x or item.y != client.y):
|
||||
await self._send_item_result(client, False, "delete", "Item is not on your square.", item.id)
|
||||
return
|
||||
can_delete_any = self._client_has_permission(client, "item.delete.any")
|
||||
can_delete_own = self._client_has_permission(client, "item.delete.own") and self._owns_item(client, item)
|
||||
if not can_delete_any and not can_delete_own:
|
||||
await self._send_item_result(client, False, "delete", "Not authorized to delete this item.", item.id)
|
||||
return
|
||||
LOGGER.info(
|
||||
"item deleted by=%s item_id=%s type=%s title=%s",
|
||||
client.nickname,
|
||||
@@ -1833,6 +2172,9 @@ class SignalingServer:
|
||||
return
|
||||
|
||||
if isinstance(packet, ItemUsePacket):
|
||||
if not self._client_has_permission(client, "item.use"):
|
||||
await self._send_item_result(client, False, "use", "Not authorized to use items.")
|
||||
return
|
||||
item = self.items.get(packet.itemId)
|
||||
if not item:
|
||||
await self._send_item_result(client, False, "use", "Item not found.")
|
||||
@@ -1918,6 +2260,9 @@ class SignalingServer:
|
||||
return
|
||||
|
||||
if isinstance(packet, ItemSecondaryUsePacket):
|
||||
if not self._client_has_permission(client, "item.use"):
|
||||
await self._send_item_result(client, False, "secondary_use", "Not authorized to use items.")
|
||||
return
|
||||
item = self.items.get(packet.itemId)
|
||||
if not item:
|
||||
await self._send_item_result(client, False, "secondary_use", "Item not found.")
|
||||
@@ -1965,6 +2310,8 @@ class SignalingServer:
|
||||
return
|
||||
|
||||
if isinstance(packet, ItemPianoNotePacket):
|
||||
if not self._client_has_permission(client, "item.use"):
|
||||
return
|
||||
item = self.items.get(packet.itemId)
|
||||
if not item or item.type != "piano":
|
||||
return
|
||||
@@ -2021,6 +2368,9 @@ class SignalingServer:
|
||||
return
|
||||
|
||||
if isinstance(packet, ItemPianoRecordingPacket):
|
||||
if not self._client_has_permission(client, "item.use"):
|
||||
await self._send_item_result(client, False, "use", "Not authorized to use items.")
|
||||
return
|
||||
item = self.items.get(packet.itemId)
|
||||
if not item or item.type != "piano":
|
||||
await self._send_item_result(client, False, "use", "Piano not found.")
|
||||
@@ -2111,6 +2461,11 @@ class SignalingServer:
|
||||
if item.carrierId is None and (item.x != client.x or item.y != client.y):
|
||||
await self._send_item_result(client, False, "update", "Item is not on your square.", item.id)
|
||||
return
|
||||
can_edit_any = self._client_has_permission(client, "item.edit.any")
|
||||
can_edit_own = self._client_has_permission(client, "item.edit.own") and self._owns_item(client, item)
|
||||
if not can_edit_any and not can_edit_own:
|
||||
await self._send_item_result(client, False, "update", "Not authorized to edit this item.", item.id)
|
||||
return
|
||||
if packet.title is not None:
|
||||
title = packet.title.strip()
|
||||
if not title:
|
||||
@@ -2136,6 +2491,8 @@ class SignalingServer:
|
||||
await self._send_item_result(client, True, "update", f"Updated {item.title}.", item.id)
|
||||
return
|
||||
|
||||
if not self._client_has_permission(client, "voice.send"):
|
||||
return
|
||||
target = self._find_by_id(packet.targetId)
|
||||
if not target:
|
||||
PACKET_LOGGER.info("signal target not found sender=%s target=%s", client.id, packet.targetId)
|
||||
|
||||
Reference in New Issue
Block a user