Skip to content

Commit aad8d0c

Browse files
committed
WL14667: Support for Multi Factor authentication (c-ext)
This worklog make it possible to connect to MySQL accounts that use multiple authentication factors. It adds connection options to specify passwords for the 2nd and 3rd factor. This feature is only available when the C extension is enabled.
1 parent 88433e1 commit aad8d0c

File tree

6 files changed

+331
-19
lines changed

6 files changed

+331
-19
lines changed

lib/mysql/connector/abstracts.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ def __init__(self, **kwargs):
9292

9393
self._user = ''
9494
self._password = ''
95+
self._password1 = ''
96+
self._password2 = ''
97+
self._password3 = ''
9598
self._database = ''
9699
self._host = '127.0.0.1'
97100
self._port = 3306
@@ -1239,7 +1242,7 @@ def cmd_ping(self):
12391242
raise NotImplementedError
12401243

12411244
def cmd_change_user(self, username='', password='', database='',
1242-
charset=45):
1245+
charset=45, password1='', password2='', password3=''):
12431246
"""Change the current logged in user"""
12441247
raise NotImplementedError
12451248

lib/mysql/connector/connection.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,11 @@ def _open_connection(self):
403403
404404
Raises on errors.
405405
"""
406+
if any((self._password1, self._password2, self._password3)):
407+
raise errors.NotSupportedError(
408+
"The Multi Factor Authentication is not supported by the "
409+
"pure Python implementation"
410+
)
406411
if self._auth_plugin == "authentication_kerberos_client":
407412
if os.name == "nt":
408413
raise errors.ProgrammingError(
@@ -999,7 +1004,7 @@ def cmd_ping(self):
9991004
return self._handle_ok(self._send_cmd(ServerCmd.PING))
10001005

10011006
def cmd_change_user(self, username='', password='', database='',
1002-
charset=45):
1007+
charset=45, password1='', password2='', password3=''):
10031008
"""Change the current logged in user
10041009
10051010
This method allows to change the current logged in user information.
@@ -1009,6 +1014,11 @@ def cmd_change_user(self, username='', password='', database='',
10091014
"""
10101015
self.handle_unread_result()
10111016

1017+
if any((password1, password2, password3)):
1018+
raise errors.NotSupportedError(
1019+
"The Multi Factor Authentication is not supported by the "
1020+
"pure Python implementation"
1021+
)
10121022
if self._compress:
10131023
raise errors.NotSupportedError("Change user is not supported with "
10141024
"compression.")
@@ -1090,7 +1100,9 @@ def reset_session(self, user_variables=None, session_variables=None):
10901100
self.cmd_reset_connection()
10911101
except errors.NotSupportedError:
10921102
self.cmd_change_user(self._user, self._password,
1093-
self._database, self._charset_id)
1103+
self._database, self._charset_id,
1104+
self._password1, self._password2,
1105+
self._password3)
10941106

10951107
cur = self.cursor()
10961108
if user_variables:

lib/mysql/connector/connection_cext.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ def _open_connection(self):
197197
'host': self._host,
198198
'user': self._user,
199199
'password': self._password,
200+
'password1': self._password1,
201+
'password2': self._password2,
202+
'password3': self._password3,
200203
'database': self._database,
201204
'port': self._port,
202205
'client_flags': self._client_flags,
@@ -691,10 +694,17 @@ def consume_results(self):
691694
self._cmysql.consume_result()
692695

693696
def cmd_change_user(self, username='', password='', database='',
694-
charset=45):
697+
charset=45, password1='', password2='', password3=''):
695698
"""Change the current logged in user"""
696699
try:
697-
self._cmysql.change_user(username, password, database)
700+
self._cmysql.change_user(
701+
username,
702+
password,
703+
database,
704+
password1,
705+
password2,
706+
password3,
707+
)
698708
except MySQLInterfaceError as exc:
699709
raise errors.get_mysql_exception(msg=exc.msg, errno=exc.errno,
700710
sqlstate=exc.sqlstate)
@@ -810,7 +820,9 @@ def reset_session(self, user_variables=None, session_variables=None):
810820
"version 5.7.2 or earlier.")
811821
else:
812822
self.cmd_change_user(self._user, self._password,
813-
self._database, self._charset_id)
823+
self._database, self._charset_id,
824+
self._password1, self._password2,
825+
self._password3)
814826

815827
if user_variables or session_variables:
816828
cur = self.cursor()

lib/mysql/connector/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
'database': None,
4747
'user': '',
4848
'password': '',
49+
'password1': '',
50+
'password2': '',
51+
'password3': '',
4952
'host': '127.0.0.1',
5053
'port': 3306,
5154
'unix_socket': None,

src/mysql_capi.c

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -910,18 +910,49 @@ PyObject*
910910
MySQL_change_user(MySQL *self, PyObject *args, PyObject *kwds)
911911
{
912912
char *user= NULL, *database= NULL;
913-
char *password = NULL;
913+
char *password= NULL, *password1=NULL, *password2= NULL, *password3= NULL;
914+
unsigned int mfa_factor1= 1, mfa_factor2= 2, mfa_factor3= 3;
914915
int res;
915-
static char *kwlist[]= {"user", "password", "database", NULL};
916+
static char *kwlist[]= {"user", "password", "database", "password1", "password2", "password3", NULL};
917+
#if MYSQL_VERSION_ID >= 80001
918+
bool abool;
919+
#else
920+
my_bool abool;
921+
#endif
916922

917923
IS_CONNECTED(self);
918924

919-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zzz", kwlist,
920-
&user, &password, &database
925+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zzzzzz", kwlist,
926+
&user, &password, &database, &password1, &password2, &password3
921927
))
922928
{
923929
return NULL;
924930
}
931+
932+
if (strcmp(PyUnicode_AsUTF8(self->auth_plugin), "mysql_clear_password") == 0)
933+
{
934+
abool= 1;
935+
mysql_options(&self->session, MYSQL_ENABLE_CLEARTEXT_PLUGIN, (char*)&abool);
936+
}
937+
938+
// Multi Factor Authentication: 1-factor password
939+
if (password1 && strlen(password1) > 0)
940+
{
941+
mysql_options4(&self->session, MYSQL_OPT_USER_PASSWORD, &mfa_factor1, password1);
942+
}
943+
944+
// Multi Factor Authentication: 2-factor password
945+
if (password2 && strlen(password2) > 0)
946+
{
947+
mysql_options4(&self->session, MYSQL_OPT_USER_PASSWORD, &mfa_factor2, password2);
948+
}
949+
950+
// Multi Factor Authentication: 3-factor password
951+
if (password3 && strlen(password3) > 0)
952+
{
953+
mysql_options4(&self->session, MYSQL_OPT_USER_PASSWORD, &mfa_factor3, password3);
954+
}
955+
925956
Py_BEGIN_ALLOW_THREADS
926957
res= mysql_change_user(&self->session, user, password, database);
927958
Py_END_ALLOW_THREADS
@@ -1132,24 +1163,36 @@ MySQL_connect(MySQL *self, PyObject *args, PyObject *kwds)
11321163
my_bool abool;
11331164
my_bool ssl_enabled= 0;
11341165
#endif
1135-
char *password = NULL;
1166+
char *password= NULL, *password1= NULL, *password2= NULL, *password3= NULL;
1167+
unsigned int mfa_factor1= 1, mfa_factor2= 2, mfa_factor3= 3;
11361168
MYSQL *res;
11371169

11381170
static char *kwlist[]=
11391171
{
1140-
"host", "user", "password", "database", "port", "unix_socket",
1141-
"client_flags", "ssl_ca", "ssl_cert", "ssl_key", "ssl_cipher_suites",
1142-
"tls_versions", "tls_cipher_suites", "ssl_verify_cert",
1172+
"host", "user", "password", "password1", "password2", "password3", "database",
1173+
"port", "unix_socket", "client_flags", "ssl_ca", "ssl_cert", "ssl_key",
1174+
"ssl_cipher_suites", "tls_versions", "tls_cipher_suites", "ssl_verify_cert",
11431175
"ssl_verify_identity", "ssl_disabled", "compress", "conn_attrs",
11441176
"local_infile", "load_data_local_dir",
11451177
NULL
11461178
};
1147-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zzzzkzkzzzzzzO!O!O!O!O!iz", kwlist,
1148-
&host, &user, &password, &database,
1149-
&port, &unix_socket,
1179+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zzzzzzzkzkzzzzzzO!O!O!O!O!iz",
1180+
kwlist,
1181+
&host,
1182+
&user,
1183+
&password,
1184+
&password1,
1185+
&password2,
1186+
&password3,
1187+
&database,
1188+
&port,
1189+
&unix_socket,
11501190
&client_flags,
1151-
&ssl_ca, &ssl_cert, &ssl_key,
1152-
&ssl_cipher_suites, &tls_versions,
1191+
&ssl_ca,
1192+
&ssl_cert,
1193+
&ssl_key,
1194+
&ssl_cipher_suites,
1195+
&tls_versions,
11531196
&tls_cipher_suites,
11541197
&PyBool_Type, &ssl_verify_cert,
11551198
&PyBool_Type, &ssl_verify_identity,
@@ -1352,6 +1395,24 @@ MySQL_connect(MySQL *self, PyObject *args, PyObject *kwds)
13521395
}
13531396
}
13541397

1398+
// Multi Factor Authentication: 1-factor password
1399+
if (password1 && strlen(password1) > 0)
1400+
{
1401+
mysql_options4(&self->session, MYSQL_OPT_USER_PASSWORD, &mfa_factor1, password1);
1402+
}
1403+
1404+
// Multi Factor Authentication: 2-factor password
1405+
if (password2 && strlen(password2) > 0)
1406+
{
1407+
mysql_options4(&self->session, MYSQL_OPT_USER_PASSWORD, &mfa_factor2, password2);
1408+
}
1409+
1410+
// Multi Factor Authentication: 3-factor password
1411+
if (password3 && strlen(password3) > 0)
1412+
{
1413+
mysql_options4(&self->session, MYSQL_OPT_USER_PASSWORD, &mfa_factor3, password3);
1414+
}
1415+
13551416
Py_BEGIN_ALLOW_THREADS
13561417
res= mysql_real_connect(&self->session, host, user, password, database,
13571418
port, unix_socket, client_flags);

0 commit comments

Comments
 (0)