16
16
import traceback
17
17
import warnings
18
18
19
+ from .auth import sha256_password_plugin as _auth
19
20
from .charset import charset_by_name , charset_by_id
20
21
from .constants import CLIENT , COMMAND , CR , FIELD_TYPE , SERVER_STATUS
21
22
from . import converters
43
44
# KeyError occurs when there's no entry in OS database for a current user.
44
45
DEFAULT_USER = None
45
46
46
-
47
47
DEBUG = False
48
48
49
49
_py_version = sys .version_info [:2 ]
@@ -107,7 +107,6 @@ def _scramble(password, message):
107
107
result = s .digest ()
108
108
return _my_crypt (result , stage1 )
109
109
110
-
111
110
def _my_crypt (message1 , message2 ):
112
111
length = len (message1 )
113
112
result = b''
@@ -186,6 +185,7 @@ def lenenc_int(i):
186
185
else :
187
186
raise ValueError ("Encoding %x is larger than %x - no representation in LengthEncodedInteger" % (i , (1 << 64 )))
188
187
188
+
189
189
class Connection (object ):
190
190
"""
191
191
Representation of a socket with a mysql server.
@@ -240,6 +240,7 @@ class Connection(object):
240
240
The class needs an authenticate method taking an authentication packet as
241
241
an argument. For the dialog plugin, a prompt(echo, prompt) method can be used
242
242
(if no authenticate method) for returning a string from the user. (experimental)
243
+ :param server_public_key: SHA256 authenticaiton plugin public key value. (default: '')
243
244
:param db: Alias for database. (for compatibility to MySQLdb)
244
245
:param passwd: Alias for password. (for compatibility to MySQLdb)
245
246
:param binary_prefix: Add _binary prefix on bytes and bytearray. (default: False)
@@ -262,7 +263,7 @@ def __init__(self, host=None, user=None, password="",
262
263
autocommit = False , db = None , passwd = None , local_infile = False ,
263
264
max_allowed_packet = 16 * 1024 * 1024 , defer_connect = False ,
264
265
auth_plugin_map = {}, read_timeout = None , write_timeout = None ,
265
- bind_address = None , binary_prefix = False ):
266
+ bind_address = None , binary_prefix = False , server_public_key = '' ):
266
267
if no_delay is not None :
267
268
warnings .warn ("no_delay option is deprecated" , DeprecationWarning )
268
269
@@ -379,6 +380,9 @@ def _config(key, arg):
379
380
self .max_allowed_packet = max_allowed_packet
380
381
self ._auth_plugin_map = auth_plugin_map
381
382
self ._binary_prefix = binary_prefix
383
+ if b"sha256_password" not in self ._auth_plugin_map :
384
+ self ._auth_plugin_map [b"sha256_password" ] = _auth .SHA256PasswordPlugin
385
+ self .server_public_key = server_public_key
382
386
if defer_connect :
383
387
self ._sock = None
384
388
else :
@@ -507,7 +511,7 @@ def select_db(self, db):
507
511
508
512
def escape (self , obj , mapping = None ):
509
513
"""Escape whatever value you pass to it.
510
-
514
+
511
515
Non-standard, for internal use; do not use this in your applications.
512
516
"""
513
517
if isinstance (obj , str_type ):
@@ -521,7 +525,7 @@ def escape(self, obj, mapping=None):
521
525
522
526
def literal (self , obj ):
523
527
"""Alias for escape()
524
-
528
+
525
529
Non-standard, for internal use; do not use this in your applications.
526
530
"""
527
531
return self .escape (obj , self .encoders )
@@ -861,6 +865,14 @@ def _request_authentication(self):
861
865
authresp = b''
862
866
if self ._auth_plugin_name in ('' , 'mysql_native_password' ):
863
867
authresp = _scramble (self .password .encode ('latin1' ), self .salt )
868
+ elif self ._auth_plugin_name == 'sha256_password' :
869
+ if self .ssl and self .server_capabilities & CLIENT .SSL :
870
+ authresp = self .password .encode ('latin1' ) + b'\0 '
871
+ else :
872
+ if self .password is not None :
873
+ authresp = b'\1 '
874
+ else :
875
+ authresp = b'\0 '
864
876
865
877
if self .server_capabilities & CLIENT .PLUGIN_AUTH_LENENC_CLIENT_DATA :
866
878
data += lenenc_int (len (authresp )) + authresp
@@ -896,24 +908,20 @@ def _request_authentication(self):
896
908
data = _scramble_323 (self .password .encode ('latin1' ), self .salt ) + b'\0 '
897
909
self .write_packet (data )
898
910
auth_packet = self ._read_packet ()
911
+ elif auth_packet .is_extra_auth_data ():
912
+ # https://dev.mysql.com/doc/internals/en/successful-authentication.html
913
+ handler = self ._get_auth_plugin_handler (self ._auth_plugin_name )
914
+ handler .authenticate (auth_packet )
899
915
900
916
def _process_auth (self , plugin_name , auth_packet ):
901
- plugin_class = self ._auth_plugin_map .get (plugin_name )
902
- if not plugin_class :
903
- plugin_class = self ._auth_plugin_map .get (plugin_name .decode ('ascii' ))
904
- if plugin_class :
917
+ handler = self ._get_auth_plugin_handler (plugin_name )
918
+ if handler != None :
905
919
try :
906
- handler = plugin_class (self )
907
920
return handler .authenticate (auth_packet )
908
921
except AttributeError :
909
922
if plugin_name != b'dialog' :
910
923
raise err .OperationalError (2059 , "Authentication plugin '%s'" \
911
924
" not loaded: - %r missing authenticate method" % (plugin_name , plugin_class ))
912
- except TypeError :
913
- raise err .OperationalError (2059 , "Authentication plugin '%s'" \
914
- " not loaded: - %r cannot be constructed with connection object" % (plugin_name , plugin_class ))
915
- else :
916
- handler = None
917
925
if plugin_name == b"mysql_native_password" :
918
926
# https://dev.mysql.com/doc/internals/en/secure-password-authentication.html#packet-Authentication::Native41
919
927
data = _scramble (self .password .encode ('latin1' ), auth_packet .read_all ())
@@ -958,6 +966,20 @@ def _process_auth(self, plugin_name, auth_packet):
958
966
pkt = self ._read_packet ()
959
967
pkt .check_error ()
960
968
return pkt
969
+
970
+ def _get_auth_plugin_handler (self , plugin_name ):
971
+ plugin_class = self ._auth_plugin_map .get (plugin_name )
972
+ if not plugin_class :
973
+ plugin_class = self ._auth_plugin_map .get (plugin_name .decode ('ascii' ))
974
+ if plugin_class :
975
+ try :
976
+ handler = plugin_class (self )
977
+ except TypeError :
978
+ raise err .OperationalError (2059 , "Authentication plugin '%s'" \
979
+ " not loaded: - %r cannot be constructed with connection object" % (plugin_name , plugin_class ))
980
+ else :
981
+ handler = None
982
+ return handler
961
983
962
984
# _mysql support
963
985
def thread_id (self ):
@@ -1232,7 +1254,7 @@ def _get_descriptions(self):
1232
1254
# This behavior is different from TEXT / BLOB.
1233
1255
# We should decode result by connection encoding regardless charsetnr.
1234
1256
# See https://github.com/PyMySQL/PyMySQL/issues/488
1235
- encoding = conn_encoding # SELECT CAST(... AS JSON)
1257
+ encoding = conn_encoding # SELECT CAST(... AS JSON)
1236
1258
elif field_type in TEXT_TYPES :
1237
1259
if field .charsetnr == 63 : # binary
1238
1260
# TEXTs with charset=binary means BINARY types.
0 commit comments