Skip to content

Commit df7830f

Browse files
committed
Handle and ignore permission errors when attempting to read .pgpass
Fixes: #356
1 parent 946e2bd commit df7830f

File tree

2 files changed

+59
-14
lines changed

2 files changed

+59
-14
lines changed

asyncpg/connect_utils.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,27 @@
5959
def _read_password_file(passfile: pathlib.Path) \
6060
-> typing.List[typing.Tuple[str, ...]]:
6161

62-
if not passfile.is_file():
63-
warnings.warn(
64-
'password file {!r} is not a plain file'.format(passfile))
62+
passtab = []
6563

66-
return None
64+
try:
65+
if not passfile.exists():
66+
return []
6767

68-
if _system != 'Windows':
69-
if passfile.stat().st_mode & (stat.S_IRWXG | stat.S_IRWXO):
68+
if not passfile.is_file():
7069
warnings.warn(
71-
'password file {!r} has group or world access; '
72-
'permissions should be u=rw (0600) or less'.format(passfile))
70+
'password file {!r} is not a plain file'.format(passfile))
7371

74-
return None
72+
return []
7573

76-
passtab = []
74+
if _system != 'Windows':
75+
if passfile.stat().st_mode & (stat.S_IRWXG | stat.S_IRWXO):
76+
warnings.warn(
77+
'password file {!r} has group or world access; '
78+
'permissions should be u=rw (0600) or less'.format(
79+
passfile))
80+
81+
return []
7782

78-
try:
7983
with passfile.open('rt') as f:
8084
for line in f:
8185
line = line.strip()
@@ -105,9 +109,6 @@ def _read_password_from_pgpass(
105109
Password string, if found, ``None`` otherwise.
106110
"""
107111

108-
if not passfile.exists():
109-
return None
110-
111112
passtab = _read_password_file(passfile)
112113
if not passtab:
113114
return None

tests/test_connect.py

+44
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,50 @@ def test_connect_pgpass_nonexistent(self):
661661
)
662662
})
663663

664+
@unittest.skipIf(_system == 'Windows', 'no mode checking on Windows')
665+
def test_connect_pgpass_inaccessible_file(self):
666+
with tempfile.NamedTemporaryFile('w+t') as passfile:
667+
os.chmod(passfile.name, stat.S_IWUSR)
668+
669+
# nonexistent passfile is OK
670+
self.run_testcase({
671+
'host': 'abc',
672+
'user': 'user',
673+
'database': 'db',
674+
'passfile': passfile.name,
675+
'result': (
676+
[('abc', 5432)],
677+
{
678+
'user': 'user',
679+
'database': 'db',
680+
}
681+
)
682+
})
683+
684+
@unittest.skipIf(_system == 'Windows', 'no mode checking on Windows')
685+
def test_connect_pgpass_inaccessible_directory(self):
686+
with tempfile.TemporaryDirectory() as passdir:
687+
with tempfile.NamedTemporaryFile('w+t', dir=passdir) as passfile:
688+
os.chmod(passdir, stat.S_IWUSR)
689+
690+
try:
691+
# nonexistent passfile is OK
692+
self.run_testcase({
693+
'host': 'abc',
694+
'user': 'user',
695+
'database': 'db',
696+
'passfile': passfile.name,
697+
'result': (
698+
[('abc', 5432)],
699+
{
700+
'user': 'user',
701+
'database': 'db',
702+
}
703+
)
704+
})
705+
finally:
706+
os.chmod(passdir, stat.S_IRWXU)
707+
664708
async def test_connect_args_validation(self):
665709
for val in {-1, 'a', True, False, 0}:
666710
with self.assertRaisesRegex(ValueError, 'greater than 0'):

0 commit comments

Comments
 (0)