Blog de développement

L'authentification à deux facteurs (2FA)

Dernière Modification le :
2000-01-01

Club Chorus Pro : faites entendre votre voix !

Le club Chorus Pro est un club utilisateurs réservé aux fournisseurs de la sphère publique,..., et aux tiers institutionnels (syndicats et associations professionnels). L'inscription est soumise à validation. Elle est gratuite et facultative.

Ce club est un réseau convivial de partage d'idées, de savoir-faire et de retours d'expériences de terrain. Ce club permet également de participer aux évolutions et aux améliorations de Chorus Pro. Lire →

https://lab.chorus-pro.gouv.fr/r/user/moderation/waiting

L'authentification à deux facteurs 2FA

Chorus Pro est un puits de productivité pour les PME. S'il existe un gain de productivité (à démontrer) il est au seul bénéfice de l'Etat qui a oublié de vous compenser votre perte de productivité à vous. Le dépot sur Chorus devrait être un poste facturable (on y pense...) Et puisque les francais aiment à se faire emmerder (confinnements idiots, faux-vaccins...) par l'exécutif et le législateur on peut y ajouter une couche! Le double authentification ralentit fortement l'interaction avec le portail Chorus Pro, sans le moindre bénéfice démontré pour l'utilisateur!

Que c'est emme...dant !!!

L'automate et 2FA

Chorus Pro, jamais à court d'idée pour embéter les PME ajoute une charge supplémentaire: l'authentification à double facteur (2FA). Ce processus ne sert à rien et ajoute une couche supplémentaire de harassement administratif.

L'automate Selenium, lui, il n'en a que faire, il fait comme on lui dit. Avant de se lancer dans le dépôt de la facture, il synchronise la boite mail (POP3).

1. Synchronisation Initiale de la Boîte Mail

Avant de tenter l'authentification, le script synchronise la boîte mail :
Connexion au Serveur POP3 Sécurisé : Le script se connecte au serveur de messagerie via POP3_SSL en utilisant les identifiants fournis.
Marquage des E-mails Existants : Les e-mails sont récupérés et leurs identifiants uniques (UIDL) enregistrés dans un fichier (processed_uids.txt). Seuls les nouveaux e-mails reçus après cette synchronisation seront analysés pour la récupérer le code 2FA.

2. Navigation vers la Page de Connexion

Ouverture de l'URL de Connexion : Selenium navigue vers l'URL de connexion de Chorus Pro.
Saisie des Identifiants : L'utilisateur et le mot de passe sont remplis automatiquement avec les valeurs en paramètres du script.
Soumission du Formulaire de Connexion : click et réponse de Chorus qui invite à saisir le code reçu par mail (comme si on n'avait que celà faire...).

3. Récupération Automatique du Code 2FA via POP3


def get_2fa_code_pop3(email_user, email_pass, pop3_server, pop3_port, uidl_file):
    """
    Récupère le code 2FA depuis la boîte mail en utilisant POP3.
    """
    # Charger les UIDL déjà traités
    processed_uids = load_processed_uids(uidl_file)
    Log("Connexion au serveur POP3 sécurisé pour récupérer le code 2FA.")
    
    try:
        # Connexion au serveur POP3 sécurisé
        mail = poplib.POP3_SSL(pop3_server, pop3_port)
        mail.user(email_user)
        mail.pass_(email_pass)
        Log("Authentification réussie sur le serveur POP3.")
        
        # Récupérer les UIDL de tous les messages
        resp, items, octets = mail.uidl()
        uid_dict = {}
        for item in items:
            parts = item.decode().split()
            if len(parts) >= 2:
                msg_num = int(parts[0])
                uid = parts[1]
                uid_dict[msg_num] = uid
        
        # Parcourir les messages à partir du dernier
        for msg_num in sorted(uid_dict.keys(), reverse=True):
            uid = uid_dict[msg_num]
            if uid in processed_uids:
                continue  # Déjà traité
            
            # Récupérer le message
            resp, lines, octets = mail.retr(msg_num)
            msg_content = b'\n'.join(lines)
            msg = BytesParser(policy=default).parsebytes(msg_content)
            
            # Vérifier l'expéditeur et le sujet
            from_field = msg['From']
            subject = msg['Subject'] or ""
            
            if "chorus-pro.gouv.fr" in from_field and "access code" in subject:
                # Extraire le corps du message
                Log("Un mail est recu de AIFE concernant access code")
                if msg.is_multipart():
                    for part in msg.walk():
                        if part.get_content_type() == 'text/plain':
                            body = part.get_content()
                            break
                else:
                    body = msg.get_content()
                
                # Extraire le code 2FA avec une regex
                match = re.search(r'\b(\d{6})\b', body)
                if match:
                    code = match.group(1)
                    Log(f"Code 2FA récupéré : {code}")
                    # Enregistrer l'UID comme traité
                    save_processed_uid(uidl_file, uid)
                    mail.quit()
                    return code
        Log("Aucun nouveau code 2FA trouvé.")
    except poplib.error_proto as e:
        Log(f"Erreur POP3: {e}")
    except Exception as e:
        Log(f"Erreur: {e}")
    
    return None
Connexion au Serveur POP3 : Le script se reconnecte au serveur de messagerie via POP3_SSL pour vérifier la réception du nouvel e-mail contenant le code 2FA.
Recherche de l'E-mail de 2FA : Il filtre les e-mails provenant de chorus-pro.gouv.fr avec le sujet contenant "access code" (en bon Français..).
Extraction du Code 2FA : Par expression régulière match = re.search(r'\b(\d{6})\b', body), le script extrait le code à six chiffres.
Mise à Jour de l'État de Connexion : l'automate document le champ et clique!

4. La vie continue...

Vous pouvez fermer la boite à conneries et continuer vers le dépôt de factures comme si de rien n'était!
Mais quelle perte de temps et d'energie! Pendant ce temps les japonais produisent, eux!
C'est le summum de la tertiarisation...
					    
driver.get(url_connexion)
    # time.sleep(10)
    try:
        login_button = WebDriverWait(driver, 30).until(EC.element_to_be_clickable((By.XPATH, "//button[@type='submit']")))
        Log("Bouton de soumission trouvé.")
        # Vous pouvez maintenant interagir avec le bouton
        login_button.click()
    except Exception as e:
        Log(f"Erreur lors de la recherche du bouton de soumission : {e}")

    # time.sleep(30)
    sauvegarder_capture_et_source(driver)
    # Remplir le champ du nom d'utilisateur
    try:
        username_field = WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.ID, 'username')))
    except TimeoutException:
        Log("L'élément 'username' n'a pas été trouvé dans le temps imparti.")
    # username_field = driver.find_element(By.ID, 'username')
    mon_identifiant = email_utilisateur
    username_field.send_keys(mon_identifiant)

    # Remplir le champ du mot de passe
    password_field = driver.find_element(By.ID, 'password')

    password_field.send_keys(mot_de_passe)

    sauvegarder_capture_et_source(driver)

    # Trouver le bouton par son type
    login_button = driver.find_element(By.XPATH, "//button[@type='submit']")
    login_button.click()

    sauvegarder_capture_et_source(driver)
    
    # Définir l'état de la connexion : 0 = échec, 1 = connecté, 2 = non connecté avec changement de mot de passe
    
    try:
        email_code_input = WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.ID, "emailCode")))
        Log("Élément 'emailCode' trouvé.")
        etat_connexion = 3  # Mettre à jour l'état de connexion
        Log(f"État de connexion mis à jour à {etat_connexion}.")
    except TimeoutException:
        Log("Erreur : Élément 'emailCode' non trouvé dans le délai imparti.")
        # driver.quit()
        etat_connexion = 0  # État par défaut : échec
        # vsys.exit(1)  # Arrêter le script avec un code d'erreur 

    # Utiliser WebDriverWait pour attendre une des deux conditions

    except TimeoutException:
        Log("Aucune des conditions n'a été remplie: on va vers 2FA")
        # driver.quit()  # Ferme le navigateur et met fin au script
        # exit ()