Skip to content

Commit 55153d4

Browse files
Bogdan Degtyariovrsomla1
Bogdan Degtyariov
authored andcommitted
WL#16645 - Option to select WebAuthN authenticator device
Change-Id: I537456d51f0ec568f96e60d8ed7386f36198c861
1 parent c28f425 commit 55153d4

File tree

6 files changed

+180
-58
lines changed

6 files changed

+180
-58
lines changed

jdbc/cppconn/connection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
"OPT_AUTHENTICATION_KERBEROS_CLIENT_MODE"
140140
#define OPT_OCI_CLIENT_CONFIG_PROFILE "OPT_OCI_CLIENT_CONFIG_PROFILE"
141141
#define OPT_OPENID_TOKEN_FILE "OPT_OPENID_TOKEN_FILE"
142+
#define OPT_WEBAUTHN_DEVICE_NUMBER "OPT_WEBAUTHN_DEVICE_NUMBER"
142143

143144
/*
144145
Telemetry options

jdbc/driver/mysql_connection.cpp

Lines changed: 108 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <algorithm>
4040
#include <random>
4141
#include <mutex>
42+
#include <unordered_set>
4243
#ifdef HAVE_STDINT_H
4344
#include <stdint.h>
4445
#endif
@@ -318,6 +319,16 @@ static const String2IntMap stringOptions[]=
318319
{OPT_LOAD_DATA_LOCAL_DIR, MYSQL_OPT_LOAD_DATA_LOCAL_DIR, false}
319320
};
320321

322+
static const std::unordered_set<std::string> stringPluginOptions = {
323+
OPT_OCI_CONFIG_FILE,
324+
OPT_AUTHENTICATION_KERBEROS_CLIENT_MODE,
325+
OPT_OCI_CLIENT_CONFIG_PROFILE,
326+
OPT_OPENID_TOKEN_FILE
327+
};
328+
329+
static const std::unordered_set<std::string> intPluginOptions = {
330+
OPT_WEBAUTHN_DEVICE_NUMBER
331+
};
321332

322333
//Option conversion for libmysqlclient < 80011
323334

@@ -1335,12 +1346,12 @@ void MySQL_Connection::init(ConnectOptionsMap & properties)
13351346
options are set here, before making a connection, to the values specified
13361347
by this connection and driver.
13371348
1338-
The guard is needed to prevent overwritting plugin options by another
1349+
The guard is needed to prevent overwriting plugin options by another
13391350
connection while this connection is being established.
13401351
13411352
Note: If connection options do not specify a value for a plugin option that
13421353
plugin option is set to null which resets it to its default value (which
1343-
could be overwriten by other connections).
1354+
could be overwritten by other connections).
13441355
13451356
TODO: Move setting of plugin options later in the connection process, after
13461357
any other options which can take long time to set (e.g. OpenSSL options or
@@ -1351,44 +1362,105 @@ void MySQL_Connection::init(ConnectOptionsMap & properties)
13511362

13521363
MySQL_Connection::PluginGuard guard{this};
13531364

1365+
/*
1366+
Set option `option` of plugin `plugin_name` of type `plugin_type` to
1367+
the value given by connection option `con_opt_name` if it is specified.
1368+
Otherwise (if the connection option is not specified) reset plugin option
1369+
value to the default value given by `default_val`.
1370+
1371+
If plugin option value could not be set throw error with description given
1372+
by `err_msg` (not if the plugin option is set to its default value).
1373+
1374+
Note that for most plugin options the default value is restored when
1375+
the option is set to null.
1376+
*/
1377+
13541378
auto set_plugin_option = [this, &properties] (
13551379
const ::sql::SQLString con_opt_name,
13561380
int plugin_type,
13571381
const ::sql::SQLString & plugin_name,
13581382
const ::sql::SQLString & option,
1359-
const char * err_msg)
1383+
const char * err_msg,
1384+
const void* default_val = nullptr
1385+
)
13601386
{
13611387
sql::SQLString *p_s = nullptr;
1388+
const void* val = nullptr;
13621389

13631390
auto opt = properties.find(con_opt_name);
13641391
if (opt != properties.end())
13651392
{
1366-
try {
1367-
p_s = (opt->second).get<sql::SQLString>();
1368-
} catch (sql::InvalidArgumentException&) {
1369-
throw sql::InvalidArgumentException(
1370-
"Wrong type passed for " + con_opt_name +
1371-
". Expected sql::SQLString.");
1393+
if (stringPluginOptions.count(con_opt_name))
1394+
{
1395+
try
1396+
{
1397+
p_s = (opt->second).get<sql::SQLString>();
1398+
if (!p_s)
1399+
throw sql::InvalidArgumentException{
1400+
"No string value passed for " + con_opt_name
1401+
};
1402+
val = p_s->c_str();
1403+
}
1404+
catch (sql::InvalidArgumentException&)
1405+
{
1406+
throw sql::InvalidArgumentException(
1407+
"Wrong type passed for " + con_opt_name +
1408+
". Expected sql::SQLString.");
1409+
}
1410+
}
1411+
else if (intPluginOptions.count(con_opt_name))
1412+
{
1413+
try
1414+
{
1415+
val = (opt->second).get<int>();
1416+
if (!val)
1417+
throw sql::InvalidArgumentException{
1418+
"No int value passed for " + con_opt_name
1419+
};
1420+
}
1421+
catch (sql::InvalidArgumentException&)
1422+
{
1423+
throw sql::InvalidArgumentException(
1424+
"Wrong type passed for " + con_opt_name +
1425+
". Expected int.");
1426+
}
1427+
}
1428+
else
1429+
{
1430+
/*
1431+
We end up here only if below this lambda is called with connection
1432+
option that is not a plugin option (not listed in
1433+
`stringPluginOptions` or `intPluginOptions` -- that should never
1434+
happen.
1435+
*/
1436+
assert(false);
13721437
}
13731438
}
13741439

1375-
/*
1376-
Note: the plugin option will be set to nullptr if the corresponding
1377-
connection option was not set. This has the effect of re-setting plugin
1378-
option to its default value (if it was changed).
1379-
*/
1440+
try
1441+
{
1442+
/*
1443+
Note: `val` is null if the connection option was not set. In that case
1444+
we reset plugin option to the default value as given by `default_val`
1445+
parameter. The last argument of `plugin_option()` informs that the
1446+
option set is the default one which is the case when `val` is null.
1447+
*/
13801448

1381-
const void* val = p_s ? p_s->c_str() : nullptr;
1382-
try {
1383-
proxy->plugin_option(plugin_type, plugin_name, option, val);
1384-
} catch (sql::InvalidArgumentException &e) {
1449+
proxy->plugin_option(
1450+
plugin_type, plugin_name, option,
1451+
val ? val : default_val, val == nullptr
1452+
);
1453+
}
1454+
catch (sql::InvalidArgumentException &e)
1455+
{
13851456
if (val)
1386-
// Throw only when setting a non-null value
1457+
// Throw only when setting to a non-default value
13871458
throw ::sql::SQLUnsupportedOptionException(err_msg,
13881459
con_opt_name.asStdString());
13891460
}
13901461
};
13911462

1463+
13921464
set_plugin_option(OPT_OCI_CONFIG_FILE,
13931465
MYSQL_CLIENT_AUTHENTICATION_PLUGIN,
13941466
"authentication_oci_client",
@@ -1419,18 +1491,30 @@ void MySQL_Connection::init(ConnectOptionsMap & properties)
14191491
"Failed to set token file for authentication_openid_connect_client plugin"
14201492
);
14211493

1494+
// Note: The default value for WebAuthN "device" option is 0.
1495+
1496+
const int webauthn_device_default_val = 0;
1497+
1498+
set_plugin_option(OPT_WEBAUTHN_DEVICE_NUMBER,
1499+
MYSQL_CLIENT_AUTHENTICATION_PLUGIN,
1500+
"authentication_webauthn_client",
1501+
"device",
1502+
"Failed to set a WebAuthn authentication device",
1503+
&webauthn_device_default_val
1504+
);
1505+
14221506
/*
14231507
Setting webauthn callback functions.
14241508
14251509
The callback is an option of the webauthn authentication plugin that
14261510
is configured on the driver level (as opposed to plugin options above,
14271511
which are configured on per-connection basis). Correctly setting the option
1428-
based on driver configuration is handled by register_webauthn_callback() function
1429-
of PluginGuard class. The option will be set only if needed.
1512+
based on driver configuration is handled by register_webauthn_callback()
1513+
function of PluginGuard class. The option will be set only if needed.
14301514
1431-
Note: If register_webauthn_callback() sets a callback then the plugin options guard
1432-
ensures that this callback function is not modified by other connections
1433-
while being used.
1515+
Note: If register_webauthn_callback() sets a callback then the plugin
1516+
options guard ensures that this callback function is not modified by other
1517+
connections while being used.
14341518
*/
14351519

14361520
guard.register_webauthn_callback(*static_cast<MySQL_Driver*>(driver));

jdbc/driver/nativeapi/mysql_native_connection_wrapper.cpp

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -563,19 +563,23 @@ struct MySQL_NativeConnectionWrapper::PluginGuard
563563

564564
int
565565
MySQL_NativeConnectionWrapper::plugin_option(
566-
int plugin_type,
567-
const ::sql::SQLString & plugin_name,
568-
const ::sql::SQLString & option,
569-
const void * value)
570-
try{
566+
int plugin_type,
567+
const ::sql::SQLString & plugin_name,
568+
const ::sql::SQLString & option,
569+
const void * value,
570+
bool default_value
571+
)
572+
try
573+
{
571574
PluginGuard guard{this};
575+
572576
/*
573-
Note: Try to load plugin into cache only if the option has non-default
574-
value.
577+
Note: Try to load plugin into cache only if the option is set to
578+
a non-default value.
575579
*/
576580

577581
struct st_mysql_client_plugin *plugin
578-
= guard.get_plugin(plugin_type, plugin_name, value != nullptr);
582+
= guard.get_plugin(plugin_type, plugin_name, !default_value);
579583

580584
/*
581585
Note: `plugin` can be null here only if we are setting option to
@@ -597,14 +601,19 @@ catch(sql::InvalidArgumentException &e)
597601
throw sql::InvalidArgumentException(err);
598602
}
599603

604+
600605
int
601606
MySQL_NativeConnectionWrapper::plugin_option(
602-
int plugin_type,
603-
const ::sql::SQLString & plugin_name,
604-
const ::sql::SQLString & option,
605-
const ::sql::SQLString & value)
606-
{
607-
return plugin_option(plugin_type, plugin_name, option, value.c_str());
607+
int plugin_type,
608+
const ::sql::SQLString & plugin_name,
609+
const ::sql::SQLString & option,
610+
const ::sql::SQLString & value,
611+
bool default_value
612+
)
613+
{
614+
return plugin_option(
615+
plugin_type, plugin_name, option, value.c_str(), default_value
616+
);
608617
}
609618

610619

jdbc/driver/nativeapi/mysql_native_connection_wrapper.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,15 @@ struct st_mysql* mysql;
141141
int get_option(::sql::mysql::MySQL_Connection_Options, const bool &) override;
142142
int get_option(::sql::mysql::MySQL_Connection_Options, const int &) override;
143143

144-
int plugin_option(int plugin_type, const ::sql::SQLString &plugin_name,
145-
const ::sql::SQLString &option, const void *) override;
146-
147-
int plugin_option(int plugin_type, const ::sql::SQLString &plugin_name,
148-
const ::sql::SQLString &option,
149-
const ::sql::SQLString &value) override;
144+
int plugin_option(
145+
int plugin_type, const ::sql::SQLString &plugin_name,
146+
const ::sql::SQLString &option, const void *, bool
147+
) override;
148+
149+
int plugin_option(
150+
int plugin_type, const ::sql::SQLString &plugin_name,
151+
const ::sql::SQLString &option, const ::sql::SQLString &value, bool
152+
) override;
150153

151154
int get_plugin_option(int plugin_type, const ::sql::SQLString &plugin_name,
152155
const ::sql::SQLString &option,

jdbc/driver/nativeapi/native_connection_wrapper.h

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -140,20 +140,34 @@ class NativeConnectionWrapper : public util::nocopy
140140
virtual int get_option(::sql::mysql::MySQL_Connection_Options,
141141
const int &) = 0;
142142

143-
virtual int plugin_option(int plugin_type,
144-
const ::sql::SQLString & plugin_name,
145-
const ::sql::SQLString & option,
146-
const void * value) = 0;
147-
148-
virtual int plugin_option(int plugin_type,
149-
const ::sql::SQLString & plugin_name,
150-
const ::sql::SQLString & option,
151-
const ::sql::SQLString & value) = 0;
152-
153-
virtual int get_plugin_option(int plugin_type,
154-
const ::sql::SQLString & plugin_name,
155-
const ::sql::SQLString & option,
156-
const ::sql::SQLString & value) = 0;
143+
/*
144+
Note: The `default_value` flag informs whether the value to which option
145+
is set is its default value. This can be used to avoid unnecessary loading
146+
of the plugin.
147+
*/
148+
149+
virtual int plugin_option(
150+
int plugin_type,
151+
const ::sql::SQLString & plugin_name,
152+
const ::sql::SQLString & option,
153+
const void * value,
154+
bool default_value = false
155+
) = 0;
156+
157+
virtual int plugin_option(
158+
int plugin_type,
159+
const ::sql::SQLString & plugin_name,
160+
const ::sql::SQLString & option,
161+
const ::sql::SQLString & value,
162+
bool default_value = false
163+
) = 0;
164+
165+
virtual int get_plugin_option(
166+
int plugin_type,
167+
const ::sql::SQLString & plugin_name,
168+
const ::sql::SQLString & option,
169+
const ::sql::SQLString & value
170+
) = 0;
157171

158172
virtual bool has_query_attributes() = 0;
159173

jdbc/test/unit/classes/connection.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4045,6 +4045,17 @@ void connection::test_fido_webauthn(sql::ConnectOptionsMap &opt, bool callback_i
40454045
if (getenv("PLUGIN_DIR"))
40464046
opt[OPT_PLUGIN_DIR] = getenv("PLUGIN_DIR");
40474047

4048+
/*
4049+
This is for extra testing of WEBAUTHN_DEVICE_NUMBER option in various
4050+
scenarios of Webauthn authentication.
4051+
*/
4052+
4053+
{
4054+
char *opt_val = getenv("WEBAUTHN_DEVICE_NUMBER");
4055+
if (opt_val)
4056+
opt[OPT_WEBAUTHN_DEVICE_NUMBER] = std::atoi(opt_val);
4057+
}
4058+
40484059
sql::Driver * driver = sql::mysql::get_driver_instance();
40494060

40504061
auto test_connection_drv = [&opt](int expected, sql::Driver *drv)

0 commit comments

Comments
 (0)