Skip to content

Commit 466fdde

Browse files
committed
PYTHON-1609 - Fix authing the same user more than once
1 parent f51723d commit 466fdde

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

pymongo/auth.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,26 @@
6161
class _Cache(object):
6262
__slots__ = ("data",)
6363

64+
_hash_val = hash('_Cache')
65+
6466
def __init__(self):
6567
self.data = None
6668

69+
def __eq__(self, other):
70+
# Two instances must always compare equal.
71+
if isinstance(other, _Cache):
72+
return True
73+
return NotImplemented
74+
75+
def __ne__(self, other):
76+
if isinstance(other, _Cache):
77+
return False
78+
return NotImplemented
79+
80+
def __hash__(self):
81+
return self._hash_val
82+
83+
6784

6885
MongoCredential = namedtuple(
6986
'MongoCredential',

test/test_auth.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,31 @@ def run(self):
7777
self.success = True
7878

7979

80+
class DBAuthenticateThread(threading.Thread):
81+
"""Used in testing threaded authentication.
82+
83+
This does db.test.find_one() with a 1-second delay to ensure it must
84+
check out and authenticate multiple sockets from the pool concurrently.
85+
86+
:Parameters:
87+
`db`: An auth-protected db with a 'test' collection containing one
88+
document.
89+
"""
90+
91+
def __init__(self, db, username, password):
92+
super(DBAuthenticateThread, self).__init__()
93+
self.db = db
94+
self.username = username
95+
self.password = password
96+
self.success = False
97+
98+
def run(self):
99+
self.db.authenticate(self.username, self.password)
100+
assert self.db.test.find_one({'$where': delay(1)}) is not None
101+
self.success = True
102+
103+
104+
80105
class TestGSSAPI(unittest.TestCase):
81106

82107
@classmethod
@@ -595,6 +620,38 @@ def test_scram_threaded(self):
595620
thread.join()
596621
self.assertTrue(thread.success)
597622

623+
class TestThreadedAuth(unittest.TestCase):
624+
625+
@client_context.require_auth
626+
def test_db_authenticate_threaded(self):
627+
628+
db = client_context.client.db
629+
coll = db.test
630+
coll.drop()
631+
coll.insert_one({'_id': 1})
632+
633+
client_context.create_user(
634+
'db',
635+
'user',
636+
'pass',
637+
roles=['dbOwner'])
638+
self.addCleanup(db.command, 'dropUser', 'user')
639+
640+
db = rs_or_single_client_noauth().db
641+
db.authenticate('user', 'pass')
642+
# No error.
643+
db.authenticate('user', 'pass')
644+
645+
db = rs_or_single_client_noauth().db
646+
threads = []
647+
for _ in range(4):
648+
threads.append(DBAuthenticateThread(db, 'user', 'pass'))
649+
for thread in threads:
650+
thread.start()
651+
for thread in threads:
652+
thread.join()
653+
self.assertTrue(thread.success)
654+
598655

599656
class TestAuthURIOptions(unittest.TestCase):
600657

0 commit comments

Comments
 (0)