Feat/broadcast all ptt#4
Open
leochely wants to merge 7 commits into
Open
Conversation
issue() accepte maintenant des kwargs supplementaires (ex: can_broadcast) qui sont stockes tels quels dans l'entree du ticket. verify_full(), nouveau, retourne l'entree complete plutot que juste le nom : permet au serveur audio de lire les capabilities par-joueur fixees au moment de l'auth sur le serveur positions. verify() (qui retourne juste le nom) est conservee pour les appelants existants - elle s'appuie maintenant sur verify_full() en interne.
Ajoute un role 'broadcaster' distinct du role admin : un joueur qui le porte peut emettre des trames audio avec le flag 0x03, relayees a tous les autres clients quels que soient leurs canaux radio. Cas d'usage : moderateurs et organisateurs d'evenements RP. Positions server (circusvoip_server.py): - _broadcasters: set persiste dans circusvoip_broadcasters.json - Helpers grant_broadcaster, revoke_broadcaster, list_broadcasters - Commandes admin associees dans _admin_handle_cmd - Le ticket emis a l'auth porte can_broadcast=(name in _broadcasters) - Le welcome ajoute 'server_caps': ['broadcast_all'] (capability discovery) + 'is_broadcaster' (etat actuel pour ce joueur) Audio server (circusvoip_audio_server.py): - state.client_caps stocke la capability lue dans le ticket au join - Avant relais, drop des trames flag 0x03 dont l'emetteur n'a pas can_broadcast=True. Log rate-limite a 1/minute par ws pour ne pas flooder si un client tient la touche en continu. Revocation : effective au prochain ticket (TTL <= 120s, soit ~2 min en pratique apres reconnexion du joueur au serveur positions). Documente dans grant_broadcaster()/revoke_broadcaster().
Ajoute un panneau BROADCASTERS dans la console admin (circusvoip_admin.py) entre PROFILS et TOKEN JOUEUR, miroir des panneaux existants : liste des broadcasters actuels avec un boutton + pour grant_broadcaster (askstring) et un croix par ligne pour revoke_broadcaster (confirmation). Aucun changement de protocole cote client joueur. Cote serveur (circusvoip_server.py): - admin_welcome contient maintenant 'broadcasters': sorted(_broadcasters) - Nouveau push 'broadcasters_list' aux admins (pas a tous, contrairement aux canaux/profils : info admin) - grant/revoke_broadcaster declenchent le push via _broadcast_broadcasters_list_threadsafe() Test fixture .test_broadcast_all.py : start positions, attente cert.pem (size > 500), puis start audio. Evite la race condition ou les deux processus generent le cert auto-signe simultanement (corrupt PEM).
Cote core (circusvoip_core.py) : - Nouveau state : broadcast_all_key, broadcast_all_active, server_supports_broadcast_all, is_broadcaster. - _on_audio_captured : nouvelle branche prioritaire avant les checks canal/profil. Quand broadcast_all_active et le serveur le supporte, emet une trame flag 0x03 (+ duplicate proximite 0x00 inchange). - Inbound handler : reconnait le flag 0x03, bypass les filtres canal/profil, applique la chaine radio (compression + effet). Toujours soumis a mute_radio. - RadioKeyListener : nouveaux helpers _on_broadcast_pressed_impl / _released_impl, miroir du profil radio. Force l'ouverture du gate audio, joue les bips locaux. - Dispatch dans _check_ptt_press / _check_ptt_release pour la touche broadcast_all_key. Cote UI (circusvoip_client.py) : - Handler welcome : lit server_caps + is_broadcaster, les stocke dans state. - Nouvelle ligne de keybind "Diffusion globale (PTT)" dans les paramètres. Desactivee avec tooltip explicatif si server_supports_broadcast_all == False (vieux serveur). - Persiste broadcast_all_key via _load_cfg / _save_cfg. README : nouvelle section "Diffusion globale (rôle broadcaster)" + ligne de keybind associee.
Le modele precedent autorisait n'importe quel client se presentant avec
le nom d'un broadcaster connu. Le shared player token + le self-declared
name ne suffisaient pas a empecher l'usurpation (un attaquant ayant le
player token pouvait se reconnecter en se nommant comme un broadcaster
legitime et heriter de can_broadcast=True).
Modele d'auth ajoute :
- circusvoip_broadcasters.json passe de list[str] a dict {name: sha256_hex}.
Ancien format detecte au load : warning + liste videe (re-grant requis).
On ne migre pas en silence : sans token push, le client cible n'aurait
rien pour se re-authentifier.
- AuthRegistry est inchange. La verification se fait en amont, dans
circusvoip_server.py via _hash_broadcaster_token / _verify_broadcaster_token
(compare_digest pour eviter les leaks par timing).
grant_broadcaster est maintenant async et requiert que le joueur cible
soit connecte. Le serveur :
- Genere un token clair (16 octets d'entropie -> 32 hex chars).
- Stocke son sha256, jamais le clair.
- Push broadcaster_token_granted directement sur la WebSocket du joueur
cible (canal authentifie deja par la connexion).
- Pousse la liste mise a jour aux admins (broadcasters_list).
Si le joueur est hors ligne : refus avec reason=player_must_be_connected.
revoke_broadcaster push broadcaster_revoked au joueur si connecte, pour
que son client efface le token local et ne tente plus de l'utiliser.
Join handler :
- Refuse si le nom est deja en ligne (name_in_use). Ferme aussi le
scenario "se reconnecter sous un autre nom pendant que mon ancien WS
est encore actif".
- Si le nom est dans _broadcasters, exige broadcaster_token verifie.
Sans token (vide) ou avec un mauvais : refus broadcaster_token_invalid.
Cela protege aussi l'identite : un non-broadcaster ne peut pas non
plus prendre le nom d'un broadcaster connu pour usurper son identite
dans le chat ou les canaux.
- can_broadcast n'est True dans le ticket que si le nom est broadcaster
ET le token a ete verifie.
Console admin (circusvoip_admin.py) :
- "+ Ajouter" remplace simpledialog.askstring par un Combobox des joueurs
actuellement connectes. Refleter la nouvelle contrainte cote serveur
(player_must_be_connected) plutot que d'afficher une erreur apres coup.
Cote core (circusvoip_core.py) :
- Nouveau helpers _get_broadcaster_token / _set_broadcaster_token /
_server_key. Stockes dans circusvoip_client_config.json sous la cle
broadcaster_tokens : {"host:port": "<token>"}. Per-server : un meme
client peut avoir des roles differents (ou n'en avoir aucun) sur
plusieurs serveurs sans collision.
- state.broadcaster_token_for_current_server : champ d'etat (pas encore
utilise UI, sert de cache interne).
Cote net (circusvoip_client.py) :
- NetWorker._ws_client lit le token sauvegarde pour (server_ip, SERVER_PORT)
juste avant d'envoyer le join, et l'ajoute au payload comme
broadcaster_token. Cle serveur memorisee pour les pushes ulterieurs.
- _handle_message : nouveau cas broadcaster_token_granted (sauve le token
via _set_broadcaster_token, met is_broadcaster=True, previent l'UI via
sig_log que l'utilisateur doit se reconnecter pour activer la touche).
- _handle_message : nouveau cas broadcaster_revoked (efface le token
local, met is_broadcaster=False).
- _handle_message : deux nouveaux reason d'erreur fatale interceptes
(name_in_use, broadcaster_token_invalid) -> stop_requested. Pas de
retry auto : l'utilisateur doit changer son nom ou demander un re-grant.
L'ancien _add_broadcaster ouvrait une popup tk.Toplevel modale avec un Combobox des joueurs connectes. Deux problemes : - Reference des constantes inexistantes (FG, BG_BTN, BG_BTN_HOVER au lieu de TEXT, BORDER, BG_ROW) -> NameError silencieux -> popup vide. - UX : un clic supplementaire et une popup juste pour selectionner un nom dans une liste qu'on a deja sous les yeux. Refonte : le dropdown vit directement dans le panneau BROADCASTERS, entre la liste des broadcasters et la section TOKEN JOUEUR. Plus de popup ; juste Combobox + bouton "Accorder". Le dropdown auto-sync via _refresh_add_broadcaster_dropdown() appele depuis refresh_players (pour les join/leave) et refresh_broadcasters (pour les grants/revokes). Si aucun joueur connecte n'est candidat -> dropdown disabled + vide. _add_broadcaster (popup) supprime, remplace par _grant_selected_broadcaster (action du bouton) qui envoie juste grant_broadcaster avec le nom selectionne.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Résumé
Ajoute un rôle broadcaster distinct du rôle admin avec un modèle d'authentification par token : un joueur à qui l'admin a accordé ce rôle peut diffuser sa voix sur tous les canaux radio simultanément, peu importe le canal des destinataires. Utile pour les modérateurs et organisateurs d'événements RP.
Couvre serveur + console admin + client. Inclut un modèle d'auth par token push-via-WSS qui ferme un trou d'usurpation de nom inhérent au modèle de player token partagé (n'importe qui avec le player token pouvait se reconnecter sous le nom d'un broadcaster connu et hériter du rôle).
Changements
Protocole audio
Nouveau byte de flag :
0x000x010x020x03Modèle d'auth broadcaster
server/circusvoip_broadcasters.jsonau format{name: sha256_hex}(le clair du token n'est jamais persisté côté serveur).AuthRegistry.issue()accepte des kwargs supplémentaires stockés dans le ticket. Nouvelle méthodeAuthRegistry.verify_full()qui retourne l'entrée complète (le serveur audio en a besoin pour lirecan_broadcast).verify()legacy conservé pour les appelants existants.grant_broadcasterest async ; requiert que le joueur soit connecté. Le serveur génère un token (16 octets d'entropie → 32 hex chars), stocke son hash, et push{"type": "broadcaster_token_granted", "token": "<clair>"}directement sur la WebSocket du joueur cible. Pas d'affichage en clair côté admin, aucune transmission hors-bande.revoke_broadcasterpush{"type": "broadcaster_revoked"}au client si connecté, pour qu'il efface son token local.broadcaster_tokendu messagejoin. Verification serveur en temps constant (secrets.compare_digest).Serveur positions (
circusvoip_server.py)grant_broadcaster {name},revoke_broadcaster {name},list_broadcasters.can_broadcast: bool(True uniquement si le token a été vérifié).welcomeétendu :server_caps: ["broadcast_all"](capability discovery)is_broadcaster: bool(état du joueur courant)admin_welcomeétendu :broadcasters: [...]. Pushbroadcasters_listenvoyé aux admins uniquement sur grant/revoke.error: name_in_use. Ferme aussi le scénario "reconnexion sous un autre nom pendant que mon ancien WS est encore actif"._broadcasters, exige unbroadcaster_tokenvalide → sinonerror: broadcaster_token_invalid. Protège l'identité même pour les non-broadcasters.Serveur audio (
circusvoip_audio_server.py)can_broadcastdu ticket viaverify_full()et le stocke dansstate.client_caps[ws].flag == 0x03et que l'émetteur n'a pascan_broadcast, drop la trame.Console admin (
circusvoip_admin.py)PROFILSetTOKEN JOUEUR.✕par ligne pour révoquer (confirmationmessagebox.askyesno).Client (
circusvoip_core.py+circusvoip_client.py)broadcast_all_key,broadcast_all_active,server_supports_broadcast_all,is_broadcaster._on_audio_captured(avant les checks canal/profil) qui émet une trame avec flag0x03(+ duplicate proximité0x00inchangé) quand la touche est maintenue et que le serveur supporte la capability._on_audio_receivedreconnaît le flag0x03, bypass les filtres canal/profil, applique la chaîne radio (compression + effet). Reste soumis àmute_radio.RadioKeyListener: nouveaux helpers_on_broadcast_pressed_impl/_released_impl, miroir du PTT profil. Force l'ouverture du gate audio et joue les bips locaux.server_supports_broadcast_all == False(vieux serveur)._get_broadcaster_token(ip, port)/_set_broadcaster_token(...). Stockage danscircusvoip_client_config.jsonsousbroadcaster_tokens: {"host:port": "<token>"}. Per-server : un même client peut avoir des rôles différents sur plusieurs serveurs.broadcaster_token_granted(sauve le token, log à l'utilisateur de se reconnecter pour activer la touche) etbroadcaster_revoked(efface le token local).name_in_use,broadcaster_token_invalid→ arrêt sans retry, l'utilisateur doit changer son nom ou demander un re-grant.Modèle de permissions et flow
broadcasterdistinct du rôle admin. Un broadcaster peut diffuser mais n'a aucun privilège admin.error: player_must_be_connected). C'est la seule fenêtre où le token peut être transmis (WS authentifiée, jamais en clair côté admin).Anti-impersonation
Le modèle player token partagé permettait à n'importe quel client avec le token de se déclarer sous n'importe quel nom au join (
data.get("name", ...)sans vérification). Combiné avec la nouvelle feature broadcaster, ça donnait un trou d'usurpation : un attaquant pouvait se reconnecter sous le nom d'un broadcaster légitime et hériter decan_broadcast=True.Cette PR ferme le trou avec deux mesures :
name_in_use).