From 2bf5501fa4295692cef712dc365f3890dc7d5571 Mon Sep 17 00:00:00 2001 From: Sicong Date: Wed, 2 Feb 2022 21:36:20 -0800 Subject: [PATCH 1/6] Update issue templates (#1369) --- .github/ISSUE_TEMPLATE/bug-report.md | 19 --------------- .github/ISSUE_TEMPLATE/bug_report.md | 28 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 9 ++++++++ 3 files changed, 37 insertions(+), 19 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 62047a12..00000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,19 +0,0 @@ -Please check the [FAQ (frequently-asked questions)](https://github.com/Microsoft/msphpsql/wiki/FAQ) first. If you have other questions or something to report, please address the following (skipping questions might delay our responses): - -### PHP Driver version or file name - -### SQL Server version - -### Client operating system - -### PHP version - -### Microsoft ODBC Driver version - -### Table schema - -### Problem description - -### Expected behavior and actual behavior - -### Repro code or steps to reproduce diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..829b0e22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +Please check the [FAQ (frequently-asked questions)](https://github.com/Microsoft/msphpsql/wiki/FAQ) first. If you have other questions or something to report, please address the following (skipping questions might delay our responses): + +**PHP version** + +**PHP SQLSRV or PDO_SQLSRV version** + +**Microsoft ODBC Driver version** + +**SQL Server version** + +**Client operating system** + +**Table schema** + +**Problem description** + +**Expected behavior and actual behavior** + +**Repro code or steps to reproduce** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 516e0e70..e1fb7815 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,3 +1,12 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + ### Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. From ba53591cf52590603d53826b3fd3bf21a618e0d2 Mon Sep 17 00:00:00 2001 From: Sicong Date: Tue, 15 Mar 2022 17:08:40 -0700 Subject: [PATCH 2/6] Fix Appveyor pipeline (#1379) --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 9a8b75d9..4cf0be78 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -73,7 +73,7 @@ install: $client.Headers.Add("user-agent", "appveyor-ci-build2"); $client.DownloadFile("http://windows.php.net/downloads/releases/sha256sum.txt", "c:\projects\sha256sum.txt"); If ($env:PHP_MINOR_VER -Match "latest") { - $env:PHP_VERSION=type c:\projects\sha256sum.txt | where { $_ -match "php-($env:PHP_MAJOR_VER\.\d+)-src" } | foreach { $matches[1] } ; + $env:PHP_VERSION=type c:\projects\sha256sum.txt | where { $_ -match "php-($env:PHP_MAJOR_VER\.\d+)-src" } | foreach { $matches[1] } | Select -First 1 ; } Else { $env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER; } 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 3/6] 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 From 8628c285414e92a339ebe30bcd7d04361ab614f0 Mon Sep 17 00:00:00 2001 From: Sicong Date: Mon, 9 May 2022 12:16:02 -0700 Subject: [PATCH 5/6] Add ActiveDirectoryIntegrated authentication (#1382) --- source/shared/core_conn.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 6ec6f597..a5f87ba3 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -697,8 +697,10 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou // Check if Authentication is ActiveDirectoryMSI because we have to handle this case differently // https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview bool activeDirectoryMSI = false; + bool activeDirectoryIntegrated = false; if (authentication_option_used) { const char aadMSIoption[] = "ActiveDirectoryMSI"; + const char addIntegratedOption[] = "ActiveDirectoryIntegrated"; zval* auth_option = NULL; auth_option = zend_hash_index_find(options, SQLSRV_CONN_OPTION_AUTHENTICATION); @@ -707,8 +709,14 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou option = Z_STRVAL_P(auth_option); } - if (option != NULL && !stricmp(option, aadMSIoption)) { - activeDirectoryMSI = true; + if (option != NULL) { + // Check if the user is using ActiveDirectoryMSI or ActiveDirectoryIntegrated + if (!stricmp(option, aadMSIoption)) { + activeDirectoryMSI = true; + } + else if (!stricmp(option, addIntegratedOption)) { + activeDirectoryIntegrated = true; + } } } @@ -730,7 +738,8 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou // 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) { + // ActiveDirectoryIntegrated does not need UID or PWD + if (!access_token_used && !activeDirectoryMSI && !activeDirectoryIntegrated) { if (uid == NULL || strnlen_s(uid) == 0) { connection_string += CONNECTION_OPTION_NO_CREDENTIALS; // "Trusted_Connection={Yes};" } From 109b8bcbc6d95894674cc8c1bd3a8500ecbf5b4c Mon Sep 17 00:00:00 2001 From: Sicong Date: Thu, 12 May 2022 16:29:34 -0700 Subject: [PATCH 6/6] Update change log and version for 5.10.1 (#1389) --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ source/shared/version.h | 6 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afdb62ed..3889d030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,33 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.10.1 - 2022-05-12 +Updated PECL release packages. Here is the list of updates: + +### Added +- Pull request [#1382](https://github.com/microsoft/msphpsql/pull/1382) - Support for ActiveDirectoryIntegrated authentication + +### Fixed +- Pull request [#1374](https://github.com/microsoft/msphpsql/pull/1374) - Fixed ActiveDirectoryMsi Authentication behavior when specified UID by laclefyoshi + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- This release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver. +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) + + ## 5.10.0 - 2022-01-31 Updated PECL release packages. Here is the list of updates: diff --git a/source/shared/version.h b/source/shared/version.h index 97407fe6..dacbd386 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -27,7 +27,7 @@ // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 #define SQLVERSION_MINOR 10 -#define SQLVERSION_PATCH 0 +#define SQLVERSION_PATCH 1 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1, 2 and so on. Otherwise, set it to 0 @@ -59,7 +59,7 @@ #define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,SQLVERSION_BUILD // PECL package version ('-' or '+' is not allowed) - to support Pickle do not use macros below -#define PHP_SQLSRV_VERSION "5.10.0" -#define PHP_PDO_SQLSRV_VERSION "5.10.0" +#define PHP_SQLSRV_VERSION "5.10.1" +#define PHP_PDO_SQLSRV_VERSION "5.10.1" #endif // VERSION_H