changeset 3049:9839ce068140

jp: password is now prompted if needed: if password is not specified on command line, it is now prompted if it is needed to start a profile session or connect it. The password is prompted with echo disabled, so somebody seing the computer screen can't see the password. fix 207
author Goffi <goffi@goffi.org>
date Tue, 01 Oct 2019 22:49:11 +0200
parents 1761e4823527
children d2a26ec74b31
files sat_frontends/jp/base.py
diffstat 1 files changed, 58 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/sat_frontends/jp/base.py	Tue Oct 01 22:49:11 2019 +0200
+++ b/sat_frontends/jp/base.py	Tue Oct 01 22:49:11 2019 +0200
@@ -46,6 +46,7 @@
 import sat_frontends.jp
 from sat_frontends.jp.loops import QuitException, getJPLoop
 from sat_frontends.jp.constants import Const as C
+from sat_frontends.bridge.bridge_frontend import BridgeException
 from sat_frontends.tools import misc
 import xml.etree.ElementTree as ET  # FIXME: used temporarily to manage XMLUI
 from collections import OrderedDict
@@ -288,7 +289,7 @@
                 "-p", "--profile", action="store", type=str, default='@DEFAULT@',
                 help=_("Use PROFILE profile key (default: %(default)s)"))
             parent.add_argument(
-                "--pwd", action="store", default='', metavar='PASSWORD',
+                "--pwd", action="store", metavar='PASSWORD',
                 help=_("Password used to connect profile, if necessary"))
 
         profile_parent, profile_session_parent = (self.parents['profile'],
@@ -773,6 +774,54 @@
 
         return dest_jids
 
+    async def a_pwd_input(self, msg=''):
+        """Like ainput but with echo disabled (useful for passwords)"""
+        # we disable echo, code adapted from getpass standard module which has been
+        # written by Piers Lauder (original), Guido van Rossum (Windows support and
+        # cleanup) and Gregory P. Smith (tty support & GetPassWarning), a big thanks
+        # to them (and for all the amazing work on Python).
+        stdin_fd = sys.stdin.fileno()
+        old = termios.tcgetattr(sys.stdin)
+        new = old[:]
+        new[3] &= ~termios.ECHO
+        tcsetattr_flags = termios.TCSAFLUSH
+        if hasattr(termios, 'TCSASOFT'):
+            tcsetattr_flags |= termios.TCSASOFT
+        try:
+            termios.tcsetattr(stdin_fd, tcsetattr_flags, new)
+            pwd = await self.ainput(msg=msg)
+        finally:
+            termios.tcsetattr(stdin_fd, tcsetattr_flags, old)
+            sys.stderr.flush()
+        self.disp('')
+        return pwd
+
+    async def connectOrPrompt(self, method, err_msg=None):
+        """Try to connect/start profile session and prompt for password if needed
+
+        @param method(callable): bridge method to either connect or start profile session
+            It will be called with password as sole argument, use lambda to do the call
+            properly
+        @param err_msg(str): message to show if connection fail
+        """
+        password = self.args.pwd
+        while True:
+            try:
+                await method(password or '')
+            except Exception as e:
+                if ((isinstance(e, BridgeException)
+                     and e.classname == 'PasswordError'
+                     and self.args.pwd is None)):
+                    if password is not None:
+                        self.disp(A.color(C.A_WARNING, _("invalid password")))
+                    password = await self.a_pwd_input(
+                        _("please enter profile password:"))
+                else:
+                    self.disp(err_msg.format(profile=self.profile, e=e), error=True)
+                    self.quit(C.EXIT_ERROR)
+            else:
+                break
+
     async def connect_profile(self):
         """Check if the profile is connected and do it if requested
 
@@ -794,11 +843,10 @@
             pass
         else:
             if start_session:
-                try:
-                    await self.bridge.profileStartSession(self.args.pwd, self.profile)
-                except Exception as e:
-                    self.disp(_(f"Can't start {self.profile}'s session: {e}"), err=True)
-                    self.quit(1)
+                await self.connectOrPrompt(
+                    lambda pwd: self.bridge.profileStartSession(pwd, self.profile),
+                    err_msg="Can't start {profile}'s session: {e}"
+                )
                 return
             elif not await self.bridge.profileIsSessionStarted(self.profile):
                 if not self.args.connect:
@@ -816,11 +864,10 @@
             # creation/deletion)
             return
         elif self.args.connect is True:  # if connection is asked, we connect the profile
-            try:
-                await self.bridge.connect(self.profile, self.args.pwd, {})
-            except Exception as e:
-                self.disp(_(f"Can't connect profile: {e}"), error=True)
-                self.quit(1)
+            await self.connectOrPrompt(
+                lambda pwd: self.bridge.connect(self.profile, pwd, {}),
+                err_msg = 'Can\'t connect profile "{profile!s}": {e}'
+            )
             return
         else:
             if not await self.bridge.isConnected(self.profile):