Skip to content

Commit 9f2cded

Browse files
committed
WL#15239: Add WebAuthn_Callback class and other refinements.
An optimization is added to set/reset callback function registered with auth plugin only when needed. Change-Id: Idc6e2a5c0f8a6dc6bb3f9f61aff7aa66ab40772f
1 parent f22fb2c commit 9f2cded

File tree

5 files changed

+320
-94
lines changed

5 files changed

+320
-94
lines changed

jdbc/cppconn/callback.h

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,103 @@ class MySQL_Connection;
4545
class MySQL_Driver;
4646
}
4747

48+
49+
/*
50+
A callback to be used with `Driver::setCallback()` to define reaction
51+
to the user action request during WebAuthn authentication handshake.
52+
53+
The client library defines default reaction which prints message on stderr.
54+
This callback can be used to change it.
55+
56+
Example usage:
57+
58+
// Use lambda
59+
60+
driver->setCallback(WebAuthn_Callback{[](SQLString msg){
61+
cerr << "User action request: " << msg << endl;
62+
}});
63+
64+
// Disable default behavior (and do nothing upon action request)
65+
66+
driver->setCallback(WebAuthn_Callback{});
67+
68+
// Return to default behavior
69+
70+
driver->setCallbacak(WebAuthn_Callback{nullptr});
71+
72+
// User defined callback
73+
74+
struct My_Callback : WebAuthn_Callback
75+
{
76+
void ActionRequested(SQLString) override;
77+
}
78+
cb;
79+
80+
driver->setCallback(cb);
81+
*/
82+
83+
class WebAuthn_Callback
84+
{
85+
std::function<void(SQLString)> callback_func;
86+
87+
public:
88+
89+
/*
90+
Create a callback that will call given lambda upon user action request.
91+
*/
92+
93+
WebAuthn_Callback(std::function<void(SQLString)>&& cb)
94+
: callback_func{std::move(cb)}
95+
{}
96+
97+
/*
98+
Create an empty callback that will do nothing upon user action request.
99+
This disables the default callback defined by the client library.
100+
*/
101+
102+
WebAuthn_Callback()
103+
: callback_func{[](SQLString){}}
104+
{}
105+
106+
/*
107+
Create a null callback. Setting such callback has the effect of using
108+
the default callback defined by the client library.
109+
*/
110+
111+
WebAuthn_Callback(std::nullptr_t)
112+
{}
113+
114+
/*
115+
Derived class can override this method to react to user action request.
116+
*/
117+
118+
virtual void ActionRequested(sql::SQLString msg)
119+
{
120+
if (callback_func)
121+
callback_func(msg);
122+
}
123+
124+
// Returns true if this callback is not null.
125+
126+
operator bool() const
127+
{
128+
return (bool)callback_func;
129+
}
130+
131+
void operator()(sql::SQLString msg)
132+
{
133+
ActionRequested(msg);
134+
}
135+
136+
};
137+
138+
48139
/*
49140
* Class that provides functionality allowing user code to set the
50141
* callback functions through inheriting, passing the callback as
51142
* constructor parameters or using lambdas.
52143
*/
144+
53145
class Fido_Callback
54146
{
55147
std::function<void(SQLString)> callback_func = nullptr;
@@ -97,6 +189,7 @@ class Fido_Callback
97189
friend class mysql::MySQL_Driver;
98190
};
99191

192+
100193
} /* namespace sql */
101194

102195
#endif // _SQL_CONNECTION_H_

jdbc/cppconn/driver.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,14 @@ class CPPCONN_PUBLIC_FUNC Driver
6060
virtual const sql::SQLString & getName() = 0;
6161

6262
virtual void setCallBack(sql::Fido_Callback &cb) = 0;
63-
6463
virtual void setCallBack(sql::Fido_Callback &&cb) = 0;
6564

6665
virtual void threadInit() = 0;
6766

6867
virtual void threadEnd() = 0;
68+
69+
virtual void setCallBack(sql::WebAuthn_Callback &cb) = 0;
70+
virtual void setCallBack(sql::WebAuthn_Callback &&cb) = 0;
6971
};
7072

7173
} /* namespace sql */

jdbc/driver/mysql_connection.cpp

Lines changed: 134 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,9 @@
9595
# define ER_MUST_CHANGE_PASSWORD_LOGIN 1820
9696
#endif
9797

98-
std::mutex callback_mutex;
99-
100-
sql::Fido_Callback * fido_callback_instance = nullptr;
10198

10299
bool oci_plugin_is_loaded = false;
103100

104-
static void fido_callback_func(const char* msg)
105-
{
106-
if (!fido_callback_instance)
107-
return;
108-
(*fido_callback_instance)(msg);
109-
}
110-
111101

112102
namespace sql
113103
{
@@ -414,6 +404,7 @@ bool get_connection_option(const sql::SQLString optionName,
414404
return false;
415405
}
416406

407+
417408
struct Prio
418409
{
419410
uint16_t prio;
@@ -429,6 +420,133 @@ struct Prio
429420
}
430421
};
431422

423+
424+
/*
425+
A callback setter object arranges for correct WebAuthn/Fido authentication
426+
callbacks to be used by the correpsonding clientlib authentication plugin.
427+
428+
If a user has registered a callback with a driver that creates a connection
429+
then a callback function is registered with authentication plugins which will
430+
call the callback stored in the driver.
431+
432+
The callback function registered with authentication plugins must be changed
433+
depending on which driver is used to create a connection. A callback setter
434+
object makes necessary changes when it detects that the current driver passed
435+
to its ctor is different from the one used last time.
436+
437+
While exists, callback setter also prevents modification of authentication
438+
plugin callbacks by concurrent threads.
439+
*/
440+
441+
struct MySQL_Driver::WebAuthn_Callback_Setter
442+
{
443+
using Proxy = NativeAPI::NativeConnectionWrapper;
444+
445+
WebAuthn_Callback_Setter(MySQL_Driver &drv, Proxy *prx)
446+
: lock{mutex}
447+
{
448+
/*
449+
Note: Meaning of callback type value:
450+
451+
0 or 3 = no callback
452+
1 or 4 = webauthn only callback
453+
2 or 5 = webauthn and fido callback
454+
455+
Values 3,4,5 indicate that the callback function has been already
456+
(de-)registered with the plugin(s).
457+
*/
458+
459+
auto callback_type = reinterpret_cast<intptr_t>(drv.fido_callback);
460+
461+
/*
462+
Do nothing if we use the same driver as last time nad (de-)registration
463+
was already done.
464+
*/
465+
466+
if ((2 < callback_type) && (driver == &drv))
467+
return;
468+
469+
driver = &drv; // Set current driver.
470+
471+
register_callback(prx, "fido", callback_type % 3 > 1);
472+
register_callback(prx, "webauthn", callback_type % 3 > 0);
473+
474+
/*
475+
Note: This will be reset to value 0-2 when user deregisters
476+
the callback or registers a new one.
477+
*/
478+
479+
drv.fido_callback = reinterpret_cast<Fido_Callback*>(callback_type + 3);
480+
}
481+
482+
private:
483+
484+
// The driver whose callback will be called by authentication plugins.
485+
486+
static sql::mysql::MySQL_Driver * driver;
487+
488+
/*
489+
Callback function to be registered with authentication plugins.
490+
If the current driver has a stored callback then it is being called.
491+
*/
492+
493+
static void callback_func(const char* msg)
494+
{
495+
if (!driver || !driver->fido_callback)
496+
return;
497+
driver->webauthn_callback(msg);
498+
}
499+
500+
static std::mutex mutex;
501+
std::lock_guard<std::mutex> lock;
502+
503+
static
504+
void register_callback(Proxy *proxy, std::string which, bool set_or_reset)
505+
{
506+
std::string plugin = "authentication_" + which + "_client";
507+
std::string opt = which + "_messages_callback";
508+
509+
try
510+
{
511+
proxy->plugin_option(MYSQL_CLIENT_AUTHENTICATION_PLUGIN,
512+
plugin.c_str(), opt.c_str(),
513+
set_or_reset ? (const void*)callback_func : nullptr
514+
);
515+
}
516+
catch (sql::MethodNotImplementedException &)
517+
{
518+
// Note: Ignore errors when re-setting the callback
519+
520+
if(!set_or_reset)
521+
return;
522+
523+
/*
524+
If failed, plugin is not present, we ignore this fact for deprected
525+
fido plugin.
526+
*/
527+
528+
if ("fido" != which)
529+
throw;
530+
}
531+
catch (sql::InvalidArgumentException &e)
532+
{
533+
if(!set_or_reset)
534+
return;
535+
536+
throw ::sql::SQLException(
537+
"Failed to set fido message callback for "
538+
+ plugin + " plugin");
539+
}
540+
};
541+
542+
};
543+
544+
545+
std::mutex MySQL_Driver::WebAuthn_Callback_Setter::mutex;
546+
sql::mysql::MySQL_Driver *MySQL_Driver::WebAuthn_Callback_Setter::driver
547+
= nullptr;
548+
549+
432550
/*
433551
We support :
434552
- hostName
@@ -1412,75 +1530,14 @@ void MySQL_Connection::init(ConnectOptionsMap & properties)
14121530
}
14131531

14141532
/*
1415-
* Helper class to simplify setting and resetting of the plugin callback.
1533+
Note: If needed the setter will update callback functions registered with
1534+
webauthn/fido authentication plugins to call the callback stored
1535+
in the driver. It also protects the callbacks from being modified while
1536+
connection is being made.
14161537
*/
1417-
struct Fido_Callback_Setter
1418-
{
1419-
NativeAPI::NativeConnectionWrapper *proxy = nullptr;
1420-
1421-
/*
1422-
* Construct the setter using the callback and proxy.
1423-
*/
1424-
Fido_Callback_Setter(Fido_Callback *callback,
1425-
NativeAPI::NativeConnectionWrapper *_proxy) :
1426-
proxy(_proxy)
1427-
{
1428-
if (proxy && callback && *callback)
1429-
{
1430-
callback_mutex.lock();
1431-
try
1432-
{
1433-
fido_callback_instance = callback;
1434-
proxy->plugin_option(MYSQL_CLIENT_AUTHENTICATION_PLUGIN,
1435-
"authentication_fido_client",
1436-
"fido_messages_callback",
1437-
(const void*)fido_callback_func
1438-
);
1439-
} catch (sql::MethodNotImplementedException &) {
1440-
// If failed, authentication_fido_client is no longer present... skip
1441-
} catch (sql::InvalidArgumentException &e) {
1442-
throw ::sql::SQLException(
1443-
"Failed to set fido message callback for "
1444-
"authentication_fido_client plugin");
1445-
}
1446-
1447-
try
1448-
{
1449-
fido_callback_instance = callback;
1450-
proxy->plugin_option(MYSQL_CLIENT_AUTHENTICATION_PLUGIN,
1451-
"authentication_webauthn_client",
1452-
"webauthn_messages_callback",
1453-
(const void *)fido_callback_func);
1454-
1455-
}
1456-
catch (sql::InvalidArgumentException& e) {
1457-
throw ::sql::SQLException(
1458-
"Failed to set webauthn message callback for "
1459-
"authentication_webauthn_client plugin");
1460-
}
1461-
}
1462-
}
1463-
1464-
~Fido_Callback_Setter()
1465-
{
1466-
if(fido_callback_instance && proxy)
1467-
{
1468-
try
1469-
{
1470-
proxy->plugin_option(MYSQL_CLIENT_AUTHENTICATION_PLUGIN,
1471-
"authentication_webauthn_client",
1472-
"webauthn_messages_callback", nullptr);
1473-
}
1474-
catch(...) {}
1475-
fido_callback_instance = nullptr;
1476-
callback_mutex.unlock();
1477-
}
1478-
}
1479-
};
14801538

1481-
Fido_Callback_Setter setter(
1482-
static_cast<MySQL_Driver*>(driver)->fido_callback,
1483-
proxy.get());
1539+
MySQL_Driver::WebAuthn_Callback_Setter
1540+
setter{*static_cast<MySQL_Driver*>(driver), proxy.get()};
14841541

14851542
//Connect loop
14861543
{

0 commit comments

Comments
 (0)