1313# limitations under the License.
1414
1515"""Authentication helpers."""
16+ from __future__ import annotations
1617
1718import functools
1819import hashlib
1920import hmac
2021import os
2122import socket
23+ import typing
2224from base64 import standard_b64decode , standard_b64encode
2325from collections import namedtuple
24- from typing import Callable , Mapping
26+ from typing import TYPE_CHECKING , Any , Callable , Mapping , MutableMapping , Optional
2527from urllib .parse import quote
2628
2729from bson .binary import Binary
3133from pymongo .errors import ConfigurationError , OperationFailure
3234from pymongo .saslprep import saslprep
3335
36+ if TYPE_CHECKING :
37+ from pymongo .hello import Hello
38+ from pymongo .pool import SocketInfo
39+
3440HAVE_KERBEROS = True
3541_USE_PRINCIPAL = False
3642try :
@@ -66,21 +72,21 @@ class _Cache:
6672
6773 _hash_val = hash ("_Cache" )
6874
69- def __init__ (self ):
75+ def __init__ (self ) -> None :
7076 self .data = None
7177
72- def __eq__ (self , other ) :
78+ def __eq__ (self , other : object ) -> bool :
7379 # Two instances must always compare equal.
7480 if isinstance (other , _Cache ):
7581 return True
7682 return NotImplemented
7783
78- def __ne__ (self , other ) :
84+ def __ne__ (self , other : object ) -> bool :
7985 if isinstance (other , _Cache ):
8086 return False
8187 return NotImplemented
8288
83- def __hash__ (self ):
89+ def __hash__ (self ) -> int :
8490 return self ._hash_val
8591
8692
@@ -101,7 +107,14 @@ def __hash__(self):
101107"""Mechanism properties for MONGODB-AWS authentication."""
102108
103109
104- def _build_credentials_tuple (mech , source , user , passwd , extra , database ):
110+ def _build_credentials_tuple (
111+ mech : str ,
112+ source : Optional [str ],
113+ user : str ,
114+ passwd : str ,
115+ extra : Mapping [str , Any ],
116+ database : Optional [str ],
117+ ) -> MongoCredential :
105118 """Build and return a mechanism specific credentials tuple."""
106119 if mech not in ("MONGODB-X509" , "MONGODB-AWS" , "MONGODB-OIDC" ) and user is None :
107120 raise ConfigurationError (f"{ mech } requires a username." )
@@ -175,17 +188,21 @@ def _build_credentials_tuple(mech, source, user, passwd, extra, database):
175188 return MongoCredential (mech , source_database , user , passwd , None , _Cache ())
176189
177190
178- def _xor (fir , sec ) :
191+ def _xor (fir : bytes , sec : bytes ) -> bytes :
179192 """XOR two byte strings together (python 3.x)."""
180193 return b"" .join ([bytes ([x ^ y ]) for x , y in zip (fir , sec )])
181194
182195
183- def _parse_scram_response (response ) :
196+ def _parse_scram_response (response : bytes ) -> dict :
184197 """Split a scram response into key, value pairs."""
185- return dict (item .split (b"=" , 1 ) for item in response .split (b"," ))
198+ return dict (
199+ typing .cast (typing .Tuple [str , str ], item .split (b"=" , 1 )) for item in response .split (b"," )
200+ )
186201
187202
188- def _authenticate_scram_start (credentials , mechanism ):
203+ def _authenticate_scram_start (
204+ credentials : MongoCredential , mechanism : str
205+ ) -> tuple [bytes , bytes , MutableMapping [str , Any ]]:
189206 username = credentials .username
190207 user = username .encode ("utf-8" ).replace (b"=" , b"=3D" ).replace (b"," , b"=2C" )
191208 nonce = standard_b64encode (os .urandom (32 ))
@@ -203,7 +220,9 @@ def _authenticate_scram_start(credentials, mechanism):
203220 return nonce , first_bare , cmd
204221
205222
206- def _authenticate_scram (credentials , sock_info , mechanism ):
223+ def _authenticate_scram (
224+ credentials : MongoCredential , sock_info : SocketInfo , mechanism : str
225+ ) -> None :
207226 """Authenticate using SCRAM."""
208227 username = credentials .username
209228 if mechanism == "SCRAM-SHA-256" :
@@ -287,7 +306,7 @@ def _authenticate_scram(credentials, sock_info, mechanism):
287306 raise OperationFailure ("SASL conversation failed to complete." )
288307
289308
290- def _password_digest (username , password ) :
309+ def _password_digest (username : str , password : str ) -> str :
291310 """Get a password digest to use for authentication."""
292311 if not isinstance (password , str ):
293312 raise TypeError ("password must be an instance of str" )
@@ -302,7 +321,7 @@ def _password_digest(username, password):
302321 return md5hash .hexdigest ()
303322
304323
305- def _auth_key (nonce , username , password ) :
324+ def _auth_key (nonce : str , username : str , password : str ) -> str :
306325 """Get an auth key to use for authentication."""
307326 digest = _password_digest (username , password )
308327 md5hash = hashlib .md5 ()
@@ -311,7 +330,7 @@ def _auth_key(nonce, username, password):
311330 return md5hash .hexdigest ()
312331
313332
314- def _canonicalize_hostname (hostname ) :
333+ def _canonicalize_hostname (hostname : str ) -> str :
315334 """Canonicalize hostname following MIT-krb5 behavior."""
316335 # https://github.com/krb5/krb5/blob/d406afa363554097ac48646a29249c04f498c88e/src/util/k5test.py#L505-L520
317336 af , socktype , proto , canonname , sockaddr = socket .getaddrinfo (
@@ -326,7 +345,7 @@ def _canonicalize_hostname(hostname):
326345 return name [0 ].lower ()
327346
328347
329- def _authenticate_gssapi (credentials , sock_info ) :
348+ def _authenticate_gssapi (credentials : MongoCredential , sock_info : SocketInfo ) -> None :
330349 """Authenticate using GSSAPI."""
331350 if not HAVE_KERBEROS :
332351 raise ConfigurationError (
@@ -443,7 +462,7 @@ def _authenticate_gssapi(credentials, sock_info):
443462 raise OperationFailure (str (exc ))
444463
445464
446- def _authenticate_plain (credentials , sock_info ) :
465+ def _authenticate_plain (credentials : MongoCredential , sock_info : SocketInfo ) -> None :
447466 """Authenticate using SASL PLAIN (RFC 4616)"""
448467 source = credentials .source
449468 username = credentials .username
@@ -460,7 +479,7 @@ def _authenticate_plain(credentials, sock_info):
460479 sock_info .command (source , cmd )
461480
462481
463- def _authenticate_x509 (credentials , sock_info ) :
482+ def _authenticate_x509 (credentials : MongoCredential , sock_info : SocketInfo ) -> None :
464483 """Authenticate using MONGODB-X509."""
465484 ctx = sock_info .auth_ctx
466485 if ctx and ctx .speculate_succeeded ():
@@ -471,7 +490,7 @@ def _authenticate_x509(credentials, sock_info):
471490 sock_info .command ("$external" , cmd )
472491
473492
474- def _authenticate_mongo_cr (credentials , sock_info ) :
493+ def _authenticate_mongo_cr (credentials : MongoCredential , sock_info : SocketInfo ) -> None :
475494 """Authenticate using MONGODB-CR."""
476495 source = credentials .source
477496 username = credentials .username
@@ -486,7 +505,7 @@ def _authenticate_mongo_cr(credentials, sock_info):
486505 sock_info .command (source , query )
487506
488507
489- def _authenticate_default (credentials , sock_info ) :
508+ def _authenticate_default (credentials : MongoCredential , sock_info : SocketInfo ) -> None :
490509 if sock_info .max_wire_version >= 7 :
491510 if sock_info .negotiated_mechs :
492511 mechs = sock_info .negotiated_mechs
@@ -518,35 +537,39 @@ def _authenticate_default(credentials, sock_info):
518537
519538
520539class _AuthContext :
521- def __init__ (self , credentials , address ) :
540+ def __init__ (self , credentials : MongoCredential , address : tuple [ str , int ]) -> None :
522541 self .credentials = credentials
523- self .speculative_authenticate = None
542+ self .speculative_authenticate : Optional [ Mapping [ str , Any ]] = None
524543 self .address = address
525544
526545 @staticmethod
527- def from_credentials (creds , address ):
546+ def from_credentials (
547+ creds : MongoCredential , address : tuple [str , int ]
548+ ) -> Optional [_AuthContext ]:
528549 spec_cls = _SPECULATIVE_AUTH_MAP .get (creds .mechanism )
529550 if spec_cls :
530551 return spec_cls (creds , address )
531552 return None
532553
533- def speculate_command (self ):
554+ def speculate_command (self ) -> Optional [ MutableMapping [ str , Any ]] :
534555 raise NotImplementedError
535556
536- def parse_response (self , hello ) :
557+ def parse_response (self , hello : Hello ) -> None :
537558 self .speculative_authenticate = hello .speculative_authenticate
538559
539- def speculate_succeeded (self ):
560+ def speculate_succeeded (self ) -> bool :
540561 return bool (self .speculative_authenticate )
541562
542563
543564class _ScramContext (_AuthContext ):
544- def __init__ (self , credentials , address , mechanism ):
565+ def __init__ (
566+ self , credentials : MongoCredential , address : tuple [str , int ], mechanism : str
567+ ) -> None :
545568 super ().__init__ (credentials , address )
546- self .scram_data = None
569+ self .scram_data : Optional [ tuple [ bytes , bytes ]] = None
547570 self .mechanism = mechanism
548571
549- def speculate_command (self ):
572+ def speculate_command (self ) -> Optional [ MutableMapping [ str , Any ]] :
550573 nonce , first_bare , cmd = _authenticate_scram_start (self .credentials , self .mechanism )
551574 # The 'db' field is included only on the speculative command.
552575 cmd ["db" ] = self .credentials .source
@@ -556,15 +579,15 @@ def speculate_command(self):
556579
557580
558581class _X509Context (_AuthContext ):
559- def speculate_command (self ):
582+ def speculate_command (self ) -> Optional [ MutableMapping [ str , Any ]] :
560583 cmd = SON ([("authenticate" , 1 ), ("mechanism" , "MONGODB-X509" )])
561584 if self .credentials .username is not None :
562585 cmd ["user" ] = self .credentials .username
563586 return cmd
564587
565588
566589class _OIDCContext (_AuthContext ):
567- def speculate_command (self ):
590+ def speculate_command (self ) -> Optional [ MutableMapping [ str , Any ]] :
568591 authenticator = _get_authenticator (self .credentials , self .address )
569592 cmd = authenticator .auth_start_cmd (False )
570593 if cmd is None :
@@ -582,7 +605,9 @@ def speculate_command(self):
582605}
583606
584607
585- def authenticate (credentials , sock_info , reauthenticate = False ):
608+ def authenticate (
609+ credentials : MongoCredential , sock_info : SocketInfo , reauthenticate : bool = False
610+ ) -> None :
586611 """Authenticate sock_info."""
587612 mechanism = credentials .mechanism
588613 auth_func = _AUTH_MAP [mechanism ]
0 commit comments