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