From a11822b154c32aed5fcf55c87730156e0bc78118 Mon Sep 17 00:00:00 2001 From: SAEKI Yoshiyasu <441570+laclefyoshi@users.noreply.github.com> Date: Sat, 19 Mar 2022 06:49:32 +0900 Subject: [PATCH] Fixed ActiveDirectoryMsi Authentication behavior when specified UID (#1374) * ActiveDirectoryMsi uid-specified support --- source/shared/core_conn.cpp | 109 ++++++++++-------- .../pdo_azure_ad_managed_identity.phpt | 34 +++++- 2 files changed, 94 insertions(+), 49 deletions(-) diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index b573e72a..6ec6f597 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -74,7 +74,7 @@ void configure_azure_key_vault( sqlsrv_conn* conn, BYTE config_attr, const DWORD void configure_azure_key_vault( sqlsrv_conn* conn, BYTE config_attr, const char* config_value, size_t key_size); std::string get_ODBC_driver_name(_In_ ODBC_DRIVER driver); #ifndef _WIN32 -bool core_search_odbc_driver_unix(_In_ ODBC_DRIVER driver); +bool core_search_odbc_driver_unix(_In_ ODBC_DRIVER driver); #endif } @@ -184,29 +184,29 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont else { // ODBC driver not specified, so check ODBC 17 first then ODBC 18 and/or ODBC 13 // If column encryption is enabled, check up to ODBC 18 - ODBC_DRIVER drivers[] = { ODBC_DRIVER::VER_17, ODBC_DRIVER::VER_18, ODBC_DRIVER::VER_13 }; - ODBC_DRIVER last_version = (conn->ce_option.enabled) ? ODBC_DRIVER::VER_18 : ODBC_DRIVER::VER_13; - + ODBC_DRIVER drivers[] = { ODBC_DRIVER::VER_17, ODBC_DRIVER::VER_18, ODBC_DRIVER::VER_13 }; + ODBC_DRIVER last_version = (conn->ce_option.enabled) ? ODBC_DRIVER::VER_18 : ODBC_DRIVER::VER_13; + ODBC_DRIVER version = ODBC_DRIVER::VER_UNKNOWN; - for (auto &d : drivers) { + for (auto &d : drivers) { std::string driver_name = get_ODBC_driver_name(d); #ifndef _WIN32 - if (core_search_odbc_driver_unix(d)) { + if (core_search_odbc_driver_unix(d)) { // now append the driver name to the connection string common_conn_str_append_func(ODBCConnOptions::Driver, driver_name.c_str(), driver_name.length(), conn_str); - r = core_odbc_connect(conn, conn_str, is_pooled); - break; - } -#else + r = core_odbc_connect(conn, conn_str, is_pooled); + break; + } +#else std::string conn_str_driver = conn_str; // use a copy of conn_str instead - common_conn_str_append_func(ODBCConnOptions::Driver, driver_name.c_str(), driver_name.length(), conn_str_driver); + common_conn_str_append_func(ODBCConnOptions::Driver, driver_name.c_str(), driver_name.length(), conn_str_driver); r = core_odbc_connect(conn, conn_str_driver, is_pooled); if (SQL_SUCCEEDED(r) || !core_compare_error_state(conn, r, "IM002")) { // something else went wrong, exit the loop now other than ODBC driver not found break; - } -#endif - else if (d == last_version) { + } +#endif + else if (d == last_version) { // if column encryption is enabled, throw the exception related to column encryption CHECK_CUSTOM_ERROR(conn->ce_option.enabled, conn, SQLSRV_ERROR_CE_DRIVER_REQUIRED, get_processor_arch()) { throw core::CoreException(); @@ -216,7 +216,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont CHECK_CUSTOM_ERROR(true, conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) { throw core::CoreException(); } - } + } } } @@ -676,8 +676,8 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou try { // Since connection options access token and authentication cannot coexist, check if both of them are used. - // If access token is specified, check UID and PWD as well. - // No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers + // If access token is specified, check UID and PWD as well. + // No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) { bool invalidOptions = false; @@ -715,6 +715,19 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou // Add the server name common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string ); + // Check uid when Authentication is ActiveDirectoryMSI + // uid can be specified when using user-assigned identity + if (activeDirectoryMSI) { + if (uid != NULL && strnlen_s(uid) > 0) { + bool escaped = core_is_conn_opt_value_escaped(uid, strnlen_s(uid)); + CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) { + throw core::CoreException(); + } + + common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string); + } + } + // If uid is not present then we use trusted connection -- but not when connecting // using the access token or Authentication is ActiveDirectoryMSI if (!access_token_used && !activeDirectoryMSI) { @@ -963,29 +976,29 @@ std::string get_ODBC_driver_name(_In_ ODBC_DRIVER driver) // Parameters: // driver - a valid value in enum ODBC_DRIVER // Return - a boolean flag that indicates if the specified driver version is found or not -bool core_search_odbc_driver_unix(_In_ ODBC_DRIVER driver) -{ - char szBuf[DEFAULT_CONN_STR_LEN + 1] = { '\0' }; // use a large enough buffer size - WORD cbBufMax = DEFAULT_CONN_STR_LEN; - WORD cbBufOut; - char *pszBuf = szBuf; - - // get all the names of the installed drivers delimited by null characters - if (!SQLGetInstalledDrivers(szBuf, cbBufMax, &cbBufOut)) - return false; - - // search for the derived ODBC driver name based on the given version - std::string driver_name = get_ODBC_driver_name(driver); - do - { - if (strstr(pszBuf, driver_name.c_str()) != 0) - return true; - - // get the next driver - pszBuf = strchr(pszBuf, '\0') + 1; - } while (pszBuf[1] != '\0'); // end when there are two consecutive null characters - - return false; +bool core_search_odbc_driver_unix(_In_ ODBC_DRIVER driver) +{ + char szBuf[DEFAULT_CONN_STR_LEN + 1] = { '\0' }; // use a large enough buffer size + WORD cbBufMax = DEFAULT_CONN_STR_LEN; + WORD cbBufOut; + char *pszBuf = szBuf; + + // get all the names of the installed drivers delimited by null characters + if (!SQLGetInstalledDrivers(szBuf, cbBufMax, &cbBufOut)) + return false; + + // search for the derived ODBC driver name based on the given version + std::string driver_name = get_ODBC_driver_name(driver); + do + { + if (strstr(pszBuf, driver_name.c_str()) != 0) + return true; + + // get the next driver + pszBuf = strchr(pszBuf, '\0') + 1; + } while (pszBuf[1] != '\0'); // end when there are two consecutive null characters + + return false; } #endif // !_WIN32 @@ -1018,15 +1031,15 @@ void driver_set_func::func(_In_ connection_option const* option, _In_ zval* valu // Check if the user provided driver_option matches any of the acceptable driver names std::string driver_option(val_str, val_len); - ODBC_DRIVER drivers[] = { ODBC_DRIVER::VER_17, ODBC_DRIVER::VER_18, ODBC_DRIVER::VER_13 }; - - conn->driver_version = ODBC_DRIVER::VER_UNKNOWN; - for (auto &d : drivers) { + ODBC_DRIVER drivers[] = { ODBC_DRIVER::VER_17, ODBC_DRIVER::VER_18, ODBC_DRIVER::VER_13 }; + + conn->driver_version = ODBC_DRIVER::VER_UNKNOWN; + for (auto &d : drivers) { std::string name = get_ODBC_driver_name(d); - if (!driver_option.compare(name)) { - conn->driver_version = d; - break; - } + if (!driver_option.compare(name)) { + conn->driver_version = d; + break; + } } CHECK_CUSTOM_ERROR(conn->driver_version == ODBC_DRIVER::VER_UNKNOWN, conn, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, Z_STRVAL_P(value)) { diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt index 23271a36..03856d90 100644 --- a/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt @@ -48,12 +48,44 @@ function connectInvalidServer() } } +function connectInvalidServerWithUser() +{ + global $server, $driver, $uid, $pwd; + + try { + $conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd); + + $msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; + $version = explode(".", $msodbcsqlVer); + + if ($version[0] < 17 || $version[1] < 3) { + //skip the rest of this test, which requires ODBC driver 17.3 or above + return; + } + unset($conn); + + // Try connecting to an invalid server, should get an exception from ODBC + $connectionInfo = "Authentication = ActiveDirectoryMsi;"; + $user = "user"; + $testCase = 'invalidServer'; + try { + $conn = new PDO("sqlsrv:server = invalidServer; $connectionInfo", $user, null); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + // TODO: check the exception message here + } + } catch(PDOException $e) { + print_r($e->getMessage()); + } +} + require_once('MsSetup.inc'); // Make a connection to an invalid server connectInvalidServer(); +connectInvalidServerWithUser(); echo "Done\n"; ?> --EXPECT-- -Done \ No newline at end of file +Done