mirror of https://github.com/python/cpython
#18116: getpass no longer always falls back to stdin.
Also fixes a resource warning that occurred when the fallback is taken. Patch by Serhiy Storchaka. (We couldn't figure out how to write tests for this.)
This commit is contained in:
parent
acb362e29f
commit
16dbbae298
|
@ -15,7 +15,11 @@ On the Mac EasyDialogs.AskPassword is used, if available.
|
||||||
# Guido van Rossum (Windows support and cleanup)
|
# Guido van Rossum (Windows support and cleanup)
|
||||||
# Gregory P. Smith (tty support & GetPassWarning)
|
# Gregory P. Smith (tty support & GetPassWarning)
|
||||||
|
|
||||||
import os, sys, warnings
|
import contextlib
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
__all__ = ["getpass","getuser","GetPassWarning"]
|
__all__ = ["getpass","getuser","GetPassWarning"]
|
||||||
|
|
||||||
|
@ -38,53 +42,57 @@ def unix_getpass(prompt='Password: ', stream=None):
|
||||||
|
|
||||||
Always restores terminal settings before returning.
|
Always restores terminal settings before returning.
|
||||||
"""
|
"""
|
||||||
fd = None
|
|
||||||
tty = None
|
|
||||||
passwd = None
|
passwd = None
|
||||||
try:
|
with contextlib.ExitStack() as stack:
|
||||||
# Always try reading and writing directly on the tty first.
|
|
||||||
fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
|
|
||||||
tty = os.fdopen(fd, 'w+', 1)
|
|
||||||
input = tty
|
|
||||||
if not stream:
|
|
||||||
stream = tty
|
|
||||||
except OSError as e:
|
|
||||||
# If that fails, see if stdin can be controlled.
|
|
||||||
try:
|
try:
|
||||||
fd = sys.stdin.fileno()
|
# Always try reading and writing directly on the tty first.
|
||||||
except (AttributeError, ValueError):
|
fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
|
||||||
passwd = fallback_getpass(prompt, stream)
|
tty = io.FileIO(fd, 'w+')
|
||||||
input = sys.stdin
|
stack.enter_context(tty)
|
||||||
if not stream:
|
input = io.TextIOWrapper(tty)
|
||||||
stream = sys.stderr
|
stack.enter_context(input)
|
||||||
|
if not stream:
|
||||||
if fd is not None:
|
stream = input
|
||||||
passwd = None
|
except OSError as e:
|
||||||
try:
|
# If that fails, see if stdin can be controlled.
|
||||||
old = termios.tcgetattr(fd) # a copy to save
|
stack.close()
|
||||||
new = old[:]
|
|
||||||
new[3] &= ~termios.ECHO # 3 == 'lflags'
|
|
||||||
tcsetattr_flags = termios.TCSAFLUSH
|
|
||||||
if hasattr(termios, 'TCSASOFT'):
|
|
||||||
tcsetattr_flags |= termios.TCSASOFT
|
|
||||||
try:
|
try:
|
||||||
termios.tcsetattr(fd, tcsetattr_flags, new)
|
fd = sys.stdin.fileno()
|
||||||
passwd = _raw_input(prompt, stream, input=input)
|
except (AttributeError, ValueError):
|
||||||
finally:
|
fd = None
|
||||||
termios.tcsetattr(fd, tcsetattr_flags, old)
|
passwd = fallback_getpass(prompt, stream)
|
||||||
stream.flush() # issue7208
|
input = sys.stdin
|
||||||
except termios.error:
|
if not stream:
|
||||||
if passwd is not None:
|
stream = sys.stderr
|
||||||
# _raw_input succeeded. The final tcsetattr failed. Reraise
|
|
||||||
# instead of leaving the terminal in an unknown state.
|
|
||||||
raise
|
|
||||||
# We can't control the tty or stdin. Give up and use normal IO.
|
|
||||||
# fallback_getpass() raises an appropriate warning.
|
|
||||||
del input, tty # clean up unused file objects before blocking
|
|
||||||
passwd = fallback_getpass(prompt, stream)
|
|
||||||
|
|
||||||
stream.write('\n')
|
if fd is not None:
|
||||||
return passwd
|
try:
|
||||||
|
old = termios.tcgetattr(fd) # a copy to save
|
||||||
|
new = old[:]
|
||||||
|
new[3] &= ~termios.ECHO # 3 == 'lflags'
|
||||||
|
tcsetattr_flags = termios.TCSAFLUSH
|
||||||
|
if hasattr(termios, 'TCSASOFT'):
|
||||||
|
tcsetattr_flags |= termios.TCSASOFT
|
||||||
|
try:
|
||||||
|
termios.tcsetattr(fd, tcsetattr_flags, new)
|
||||||
|
passwd = _raw_input(prompt, stream, input=input)
|
||||||
|
finally:
|
||||||
|
termios.tcsetattr(fd, tcsetattr_flags, old)
|
||||||
|
stream.flush() # issue7208
|
||||||
|
except termios.error:
|
||||||
|
if passwd is not None:
|
||||||
|
# _raw_input succeeded. The final tcsetattr failed. Reraise
|
||||||
|
# instead of leaving the terminal in an unknown state.
|
||||||
|
raise
|
||||||
|
# We can't control the tty or stdin. Give up and use normal IO.
|
||||||
|
# fallback_getpass() raises an appropriate warning.
|
||||||
|
if stream is not input:
|
||||||
|
# clean up unused file objects before blocking
|
||||||
|
stack.close()
|
||||||
|
passwd = fallback_getpass(prompt, stream)
|
||||||
|
|
||||||
|
stream.write('\n')
|
||||||
|
return passwd
|
||||||
|
|
||||||
|
|
||||||
def win_getpass(prompt='Password: ', stream=None):
|
def win_getpass(prompt='Password: ', stream=None):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import getpass
|
import getpass
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
from io import StringIO
|
from io import BytesIO, StringIO
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from test import support
|
from test import support
|
||||||
|
|
||||||
|
@ -88,7 +88,8 @@ class UnixGetpassTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_uses_tty_directly(self):
|
def test_uses_tty_directly(self):
|
||||||
with mock.patch('os.open') as open, \
|
with mock.patch('os.open') as open, \
|
||||||
mock.patch('os.fdopen'):
|
mock.patch('io.FileIO') as fileio, \
|
||||||
|
mock.patch('io.TextIOWrapper') as textio:
|
||||||
# By setting open's return value to None the implementation will
|
# By setting open's return value to None the implementation will
|
||||||
# skip code we don't care about in this test. We can mock this out
|
# skip code we don't care about in this test. We can mock this out
|
||||||
# fully if an alternate implementation works differently.
|
# fully if an alternate implementation works differently.
|
||||||
|
@ -96,10 +97,13 @@ class UnixGetpassTest(unittest.TestCase):
|
||||||
getpass.unix_getpass()
|
getpass.unix_getpass()
|
||||||
open.assert_called_once_with('/dev/tty',
|
open.assert_called_once_with('/dev/tty',
|
||||||
os.O_RDWR | os.O_NOCTTY)
|
os.O_RDWR | os.O_NOCTTY)
|
||||||
|
fileio.assert_called_once_with(open.return_value, 'w+')
|
||||||
|
textio.assert_called_once_with(fileio.return_value)
|
||||||
|
|
||||||
def test_resets_termios(self):
|
def test_resets_termios(self):
|
||||||
with mock.patch('os.open') as open, \
|
with mock.patch('os.open') as open, \
|
||||||
mock.patch('os.fdopen'), \
|
mock.patch('io.FileIO'), \
|
||||||
|
mock.patch('io.TextIOWrapper'), \
|
||||||
mock.patch('termios.tcgetattr') as tcgetattr, \
|
mock.patch('termios.tcgetattr') as tcgetattr, \
|
||||||
mock.patch('termios.tcsetattr') as tcsetattr:
|
mock.patch('termios.tcsetattr') as tcsetattr:
|
||||||
open.return_value = 3
|
open.return_value = 3
|
||||||
|
@ -110,21 +114,23 @@ class UnixGetpassTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_falls_back_to_fallback_if_termios_raises(self):
|
def test_falls_back_to_fallback_if_termios_raises(self):
|
||||||
with mock.patch('os.open') as open, \
|
with mock.patch('os.open') as open, \
|
||||||
mock.patch('os.fdopen') as fdopen, \
|
mock.patch('io.FileIO') as fileio, \
|
||||||
|
mock.patch('io.TextIOWrapper') as textio, \
|
||||||
mock.patch('termios.tcgetattr'), \
|
mock.patch('termios.tcgetattr'), \
|
||||||
mock.patch('termios.tcsetattr') as tcsetattr, \
|
mock.patch('termios.tcsetattr') as tcsetattr, \
|
||||||
mock.patch('getpass.fallback_getpass') as fallback:
|
mock.patch('getpass.fallback_getpass') as fallback:
|
||||||
open.return_value = 3
|
open.return_value = 3
|
||||||
fdopen.return_value = StringIO()
|
fileio.return_value = BytesIO()
|
||||||
tcsetattr.side_effect = termios.error
|
tcsetattr.side_effect = termios.error
|
||||||
getpass.unix_getpass()
|
getpass.unix_getpass()
|
||||||
fallback.assert_called_once_with('Password: ',
|
fallback.assert_called_once_with('Password: ',
|
||||||
fdopen.return_value)
|
textio.return_value)
|
||||||
|
|
||||||
def test_flushes_stream_after_input(self):
|
def test_flushes_stream_after_input(self):
|
||||||
# issue 7208
|
# issue 7208
|
||||||
with mock.patch('os.open') as open, \
|
with mock.patch('os.open') as open, \
|
||||||
mock.patch('os.fdopen'), \
|
mock.patch('io.FileIO'), \
|
||||||
|
mock.patch('io.TextIOWrapper'), \
|
||||||
mock.patch('termios.tcgetattr'), \
|
mock.patch('termios.tcgetattr'), \
|
||||||
mock.patch('termios.tcsetattr'):
|
mock.patch('termios.tcsetattr'):
|
||||||
open.return_value = 3
|
open.return_value = 3
|
||||||
|
|
|
@ -142,6 +142,10 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #18116: getpass was always getting an error when testing /dev/tty,
|
||||||
|
and thus was always falling back to stdin. It also leaked an open file
|
||||||
|
when it did so. Both of these issues are now fixed.
|
||||||
|
|
||||||
- Issue #17198: Fix a NameError in the dbm module. Patch by Valentina
|
- Issue #17198: Fix a NameError in the dbm module. Patch by Valentina
|
||||||
Mukhamedzhanova.
|
Mukhamedzhanova.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue