comparison sat_frontends/jp/base.py @ 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 d9f328374473
children ee2654308a57
comparison
equal deleted inserted replaced
3048:1761e4823527 3049:9839ce068140
44 from sat.tools.common.ansi import ANSI as A 44 from sat.tools.common.ansi import ANSI as A
45 from sat.core import exceptions 45 from sat.core import exceptions
46 import sat_frontends.jp 46 import sat_frontends.jp
47 from sat_frontends.jp.loops import QuitException, getJPLoop 47 from sat_frontends.jp.loops import QuitException, getJPLoop
48 from sat_frontends.jp.constants import Const as C 48 from sat_frontends.jp.constants import Const as C
49 from sat_frontends.bridge.bridge_frontend import BridgeException
49 from sat_frontends.tools import misc 50 from sat_frontends.tools import misc
50 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI 51 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
51 from collections import OrderedDict 52 from collections import OrderedDict
52 53
53 ## bridge handling 54 ## bridge handling
286 parent = self.parents[parent_name] = argparse.ArgumentParser(add_help=False) 287 parent = self.parents[parent_name] = argparse.ArgumentParser(add_help=False)
287 parent.add_argument( 288 parent.add_argument(
288 "-p", "--profile", action="store", type=str, default='@DEFAULT@', 289 "-p", "--profile", action="store", type=str, default='@DEFAULT@',
289 help=_("Use PROFILE profile key (default: %(default)s)")) 290 help=_("Use PROFILE profile key (default: %(default)s)"))
290 parent.add_argument( 291 parent.add_argument(
291 "--pwd", action="store", default='', metavar='PASSWORD', 292 "--pwd", action="store", metavar='PASSWORD',
292 help=_("Password used to connect profile, if necessary")) 293 help=_("Password used to connect profile, if necessary"))
293 294
294 profile_parent, profile_session_parent = (self.parents['profile'], 295 profile_parent, profile_session_parent = (self.parents['profile'],
295 self.parents['profile_session']) 296 self.parents['profile_session'])
296 297
771 except AttributeError: 772 except AttributeError:
772 pass 773 pass
773 774
774 return dest_jids 775 return dest_jids
775 776
777 async def a_pwd_input(self, msg=''):
778 """Like ainput but with echo disabled (useful for passwords)"""
779 # we disable echo, code adapted from getpass standard module which has been
780 # written by Piers Lauder (original), Guido van Rossum (Windows support and
781 # cleanup) and Gregory P. Smith (tty support & GetPassWarning), a big thanks
782 # to them (and for all the amazing work on Python).
783 stdin_fd = sys.stdin.fileno()
784 old = termios.tcgetattr(sys.stdin)
785 new = old[:]
786 new[3] &= ~termios.ECHO
787 tcsetattr_flags = termios.TCSAFLUSH
788 if hasattr(termios, 'TCSASOFT'):
789 tcsetattr_flags |= termios.TCSASOFT
790 try:
791 termios.tcsetattr(stdin_fd, tcsetattr_flags, new)
792 pwd = await self.ainput(msg=msg)
793 finally:
794 termios.tcsetattr(stdin_fd, tcsetattr_flags, old)
795 sys.stderr.flush()
796 self.disp('')
797 return pwd
798
799 async def connectOrPrompt(self, method, err_msg=None):
800 """Try to connect/start profile session and prompt for password if needed
801
802 @param method(callable): bridge method to either connect or start profile session
803 It will be called with password as sole argument, use lambda to do the call
804 properly
805 @param err_msg(str): message to show if connection fail
806 """
807 password = self.args.pwd
808 while True:
809 try:
810 await method(password or '')
811 except Exception as e:
812 if ((isinstance(e, BridgeException)
813 and e.classname == 'PasswordError'
814 and self.args.pwd is None)):
815 if password is not None:
816 self.disp(A.color(C.A_WARNING, _("invalid password")))
817 password = await self.a_pwd_input(
818 _("please enter profile password:"))
819 else:
820 self.disp(err_msg.format(profile=self.profile, e=e), error=True)
821 self.quit(C.EXIT_ERROR)
822 else:
823 break
824
776 async def connect_profile(self): 825 async def connect_profile(self):
777 """Check if the profile is connected and do it if requested 826 """Check if the profile is connected and do it if requested
778 827
779 @exit: - 1 when profile is not connected and --connect is not set 828 @exit: - 1 when profile is not connected and --connect is not set
780 - 1 when the profile doesn't exists 829 - 1 when the profile doesn't exists
792 start_session = self.args.start_session 841 start_session = self.args.start_session
793 except AttributeError: 842 except AttributeError:
794 pass 843 pass
795 else: 844 else:
796 if start_session: 845 if start_session:
797 try: 846 await self.connectOrPrompt(
798 await self.bridge.profileStartSession(self.args.pwd, self.profile) 847 lambda pwd: self.bridge.profileStartSession(pwd, self.profile),
799 except Exception as e: 848 err_msg="Can't start {profile}'s session: {e}"
800 self.disp(_(f"Can't start {self.profile}'s session: {e}"), err=True) 849 )
801 self.quit(1)
802 return 850 return
803 elif not await self.bridge.profileIsSessionStarted(self.profile): 851 elif not await self.bridge.profileIsSessionStarted(self.profile):
804 if not self.args.connect: 852 if not self.args.connect:
805 self.disp(_( 853 self.disp(_(
806 f"Session for [{self.profile}] is not started, please start it " 854 f"Session for [{self.profile}] is not started, please start it "
814 if not hasattr(self.args, 'connect'): 862 if not hasattr(self.args, 'connect'):
815 # a profile can be present without connect option (e.g. on profile 863 # a profile can be present without connect option (e.g. on profile
816 # creation/deletion) 864 # creation/deletion)
817 return 865 return
818 elif self.args.connect is True: # if connection is asked, we connect the profile 866 elif self.args.connect is True: # if connection is asked, we connect the profile
819 try: 867 await self.connectOrPrompt(
820 await self.bridge.connect(self.profile, self.args.pwd, {}) 868 lambda pwd: self.bridge.connect(self.profile, pwd, {}),
821 except Exception as e: 869 err_msg = 'Can\'t connect profile "{profile!s}": {e}'
822 self.disp(_(f"Can't connect profile: {e}"), error=True) 870 )
823 self.quit(1)
824 return 871 return
825 else: 872 else:
826 if not await self.bridge.isConnected(self.profile): 873 if not await self.bridge.isConnected(self.profile):
827 log.error( 874 log.error(
828 _(f"Profile [{self.profile}] is not connected, please connect it " 875 _(f"Profile [{self.profile}] is not connected, please connect it "