Cómo y dónde recuperar mejor la contraseña de sudo a través de una GUI nativa en una aplicación macOS Python – (mientras se mantiene una secuencia de salida interactiva (stdout))

Ok, entonces la situación es la siguiente: estoy creando una aplicación macOS GUI usando Python y wx (wxphoenix). El usuario puede usar la GUI (por ejemplo: script1 ) para iniciar un proceso de eliminación de archivos (contenido en script2 ). Para ejecutar con éxito, script2 debe ejecutarse con derechos sudo.

script2 se repetirá sobre una larga lista de archivos y los eliminará. Pero lo necesito para comunicarme con la GUI contenida en script1 después de cada ronda para que script1 pueda actualizar la script1 progreso.

En su forma más básica, mi configuración de trabajo actual se ve así:

Script1:

 import io from threading import Thread import subprocess import wx # a whole lot of wx GUI stuff def get_password(): """Retrieve user password via a GUI""" # A wx solution using wx.PasswordEntryDialog() # Store password in a variable return variable class run_script_with_sudo(Thread): """Launch a script with administrator privileges""" def __init__(self, path_to_script, wx_pubsub_sendmessage): """Set variables to self""" self.path = path_to_script self.sender = wx_pubsub_sendmessage self.password = get_password() Thread.__init__(self) self.start() def run(self): """Run thread""" prepare_script = subprocess.Popen(["echo", password], stdout=subprocess.PIPE) prepare_script.wait() launch_script = subprocess.Popen(['sudo', '-S', '/usr/local/bin/python3.6', '-u', self.path], stdin=prepare_script.stdout, stdout=subprocess.PIPE) for line in io.TextIOWrapper(launch_script.stdout, encoding="utf-8"): print("Received line: ", line.rstrip()) # Tell progressbar to add another step: wx.CallAfter(self.sender, "update", msg="") 

Script2:

 import time # This is a test setup, just a very simple loop that produces an output. for i in range(25): time.sleep(1) print(i) 

La configuración anterior funciona en ese script1 recibe la salida de script2 en tiempo real y actúa en script2 . (Entonces, en el ejemplo dado: después de cada segundo script1 agrega otro paso a la barra de progreso hasta que alcanza los 25 pasos).

Lo que quiero lograr = no almacenar la contraseña en una variable y usar macOS es su GUI nativa para recuperar la contraseña.

Sin embargo cuando cambio:

 prepare_script = subprocess.Popen(["echo", password], stdout=subprocess.PIPE) prepare_script.wait() launch_script = subprocess.Popen(['sudo', '-S', '/usr/local/bin/python3.6', '-u', self.path], stdin=prepare_script.stdout, stdout=subprocess.PIPE) for line in io.TextIOWrapper(launch_script.stdout, encoding="utf-8"): print("Received line: ", line.rstrip()) # Tell progressbar to add another step: wx.CallAfter(self.sender, "update", msg="") 

Dentro:

 command = r"""/usr/bin/osascript -e 'do shell script "/usr/local/bin/python3.6 -u """ + self.path + """ with prompt "Sart Deletion Process " with administrator privileges'""" command_list = shlex.split(command) launch_script = subprocess.Popen(command_list, stdout=subprocess.PIPE) for line in io.TextIOWrapper(launch_script.stdout, encoding="utf-8"): print("Received line: ", line.rstrip()) # Tell progressbar to add another step: wx.CallAfter(self.sender, "update", msg="") 

Deja de funcionar porque osascript aparentemente se ejecuta en un shell no interactivo . Esto significa que script2 no envía ninguna salida hasta que esté completamente terminado, lo que hace que la barra de progreso en script1 detenga.

Mi pregunta se convierte entonces en : ¿Cómo puedo asegurarme de usar la GUI nativa de macOS para solicitar la contraseña sudo, evitando así tener que almacenarla en una variable, mientras se mantiene la posibilidad de capturar la salida estándar del script privilegiado de forma interactiva / real? en tiempo real

Espero que tenga sentido.

¡Apreciaría cualquier idea!

Mi pregunta se convierte entonces en : ¿Cómo puedo asegurarme de usar la GUI nativa de macOS para solicitar la contraseña sudo, evitando así tener que almacenarla en una variable, mientras se mantiene la posibilidad de capturar la salida estándar del script privilegiado de forma interactiva / real? en tiempo real

Yo mismo he encontrado una solución, utilizando una canalización con nombre ( os.mkfifo() ).

De esa manera, puede hacer que 2 scripts de Python se comuniquen entre sí, mientras que 1 de ellos se inicia con derechos privilegiados a través de osascript (es decir, obtiene una ventana GUI nativa que solicita la contraseña sudo de los usuarios).

Solución de trabajo :

mainscript.py

 import os from pathlib import Path import shlex import subprocess import sys from threading import Thread import time class LaunchDeletionProcess(Thread): def __init__(self): Thread.__init__(self) def run(self): launch_command = r"""/usr/bin/osascript -e 'do shell script "/usr/local/bin/python3.6 -u /path/to/priviliged_script.py" with prompt "Sart Deletion Process " with administrator privileges'""" split_command = shlex.split(launch_command) print("Thread 1 started") testprogram = subprocess.Popen(split_command) testprogram.wait() print("Thread1 Finished") class ReadStatus(Thread): def __init__(self): Thread.__init__(self) def run(self): while not os.path.exists(os.path.expanduser("~/p1")): time.sleep(0.1) print("Thread 2 started") self.wfPath = os.path.expanduser("~/p1") rp = open(self.wfPath, 'r') response = rp.read() self.try_pipe(response) def try_pipe(self, response): rp = open(self.wfPath, 'r') response = rp.read() print("Receiving response: ", response) rp.close() if response == str(self.nr_of_steps-1): print("Got to end") os.remove(os.path.expanduser("~/p1")) else: time.sleep(1) self.try_pipe(response) if __name__ == "__main__": thread1 = LaunchDeletionProcess() thread2 = ReadStatus() thread1.start() thread2.start() 

priviliged_script.py

 import os import time import random wfPath = os.path.expanduser("~/p1") try: os.mkfifo(wfPath) except OSError: print("error") pass result = 10 nr = 0 while nr < result: random_nr = random.random() wp = open(wfPath, 'w') print("writing new number: ", random_nr) wp.write("Number: " + str(random_nr)) wp.close() time.sleep(1) nr += 1 wp = open(wfPath, 'w') wp.write("end") wp.close()