Dotfiles_ArchAP/greetd/src/auth.py

85 lines
2.9 KiB
Python

"""Machine PAM indépendante de GTK pour préserver les invites multiples."""
from greetd_ipc import Client, ProtocolError, session_command
class AuthFlow:
def __init__(self, socket_path=None):
self.socket_path = socket_path
self.client = None
self.username = None
self.pending = None
self.authenticated = False
self.selection = None
def begin(self, username, selection="stable"):
self.reset()
session_command(selection)
if type(username) is not str or not username or len(username) > 256 or any(ord(c) < 32 for c in username):
raise ValueError("Nom utilisateur invalide")
# CONTRACT: compte et choix sont figés pour toute cette conversation PAM.
# Le thread GTK ne les modifie pas ; un nouveau begin annule l'ancien état.
self.username = username
self.selection = selection
try:
self.client = Client(self.socket_path)
return self._accept(self.client.create(username))
except (OSError, ProtocolError):
self.reset()
raise
def answer(self, value=None):
if self.pending is None or self.client is None:
raise ProtocolError("Aucune invite PAM active")
kind = self.pending["auth_message_type"]
if kind in ("secret", "visible") and value is None:
raise ProtocolError("Réponse PAM requise")
if kind in ("info", "error"):
value = None
self.pending = None
try:
return self._accept(self.client.answer(value))
except (OSError, ProtocolError):
self.reset()
raise
def _accept(self, reply):
self.pending = None
if reply["type"] == "auth_message":
self.pending = reply
elif reply["type"] == "success":
self.authenticated = True
elif reply["type"] == "error":
self.reset()
return reply
def start(self):
if not self.authenticated or self.client is None:
raise ProtocolError("Session non authentifiée")
# INVARIANT: un seul start par authentification, succès ou refus ; aucun repli.
self.authenticated = False
try:
reply = self.client.start(self.selection)
except (OSError, ProtocolError):
self.reset()
raise
if reply["type"] == "success":
self.client.close()
self.client = None
self.username = None
self.selection = None
else:
self.reset()
return reply
def reset(self):
if self.client is not None:
try:
self.client.cancel()
except (OSError, ProtocolError):
pass
self.client.close()
self.client = None
self.username = None
self.selection = None
self.pending = None
self.authenticated = False