Adding supporting for Azure AD access token (#837)

* Adding supporting for Azure AD access token

* Added more comments for the AD access token skipif files

* Save the pointer to access token struct until after connecting

* Clear the access token data before freeing the memory

* Added a reference as per review
This commit is contained in:
Jenny Tam 2018-09-06 11:32:04 -07:00 committed by GitHub
parent ae1b413f19
commit e51380612d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 535 additions and 28 deletions

View file

@ -38,6 +38,7 @@ namespace PDOConnOptionNames {
const char Server[] = "Server";
const char APP[] = "APP";
const char AccessToken[] = "AccessToken";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
@ -185,6 +186,15 @@ const connection_option PDO_CONN_OPTS[] = {
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
PDOConnOptionNames::AccessToken,
sizeof( PDOConnOptionNames::AccessToken ),
SQLSRV_CONN_OPTION_ACCESS_TOKEN,
ODBCConnOptions::AccessToken,
sizeof( ODBCConnOptions::AccessToken),
CONN_ATTR_STRING,
access_token_set_func::func
},
{
PDOConnOptionNames::ApplicationIntent,
sizeof( PDOConnOptionNames::ApplicationIntent ),

View file

@ -429,6 +429,15 @@ pdo_error PDO_ERRORS[] = {
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -89, false}
},
{
SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.", -90, false}
},
{
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false}
},
{ UINT_MAX, {} }
};

View file

@ -243,6 +243,12 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
} // else driver_version not unknown
#endif // !_WIN32
// time to free the access token, if not null
if (conn->azure_ad_access_token != NULL) {
memset(conn->azure_ad_access_token->data, 0, conn->azure_ad_access_token->dataSize); // clear the memory
conn->azure_ad_access_token.reset();
}
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
}
@ -759,35 +765,53 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
{
bool mars_mentioned = false;
connection_option const* conn_opt;
bool access_token_used = false;
try {
// First of all, check if access token is specified. If so, check if UID, PWD, Authentication exist
// 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;
// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC );
// UID and PWD have to be NULLs... throw an exception as long as the user has specified any of them in the connection string,
// even if they may be empty strings. Likewise if the keyword Authentication exists
if (uid != NULL || pwd != NULL || zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION)) {
invalidOptions = true;
}
// if uid is not present then we use trusted connection.
if(uid == NULL || strnlen_s( uid ) == 0 ) {
connection_string += "Trusted_Connection={Yes};";
}
else {
bool escaped = core_is_conn_opt_value_escaped( uid, strnlen_s( uid ));
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
CHECK_CUSTOM_ERROR(invalidOptions, conn, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN ) {
throw core::CoreException();
}
common_conn_str_append_func( ODBCConnOptions::UID, uid, strnlen_s( uid ), connection_string TSRMLS_CC );
access_token_used = true;
}
// if no password was given, then don't add a password to the connection string. Perhaps the UID
// given doesn't have a password?
if( pwd != NULL ) {
escaped = core_is_conn_opt_value_escaped( pwd, strnlen_s( pwd ));
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC );
// if uid is not present then we use trusted connection -- but not when access token is used, because they are incompatible
if (!access_token_used) {
if (uid == NULL || strnlen_s(uid) == 0) {
connection_string += CONNECTION_OPTION_NO_CREDENTIALS; // "Trusted_Connection={Yes};"
}
else {
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::PWD, pwd, strnlen_s( pwd ), connection_string TSRMLS_CC );
common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string TSRMLS_CC);
// if no password was given, then don't add a password to the connection string. Perhaps the UID
// given doesn't have a password?
if (pwd != NULL) {
escaped = core_is_conn_opt_value_escaped(pwd, strnlen_s(pwd));
CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) {
throw core::CoreException();
}
common_conn_str_append_func(ODBCConnOptions::PWD, pwd, strnlen_s(pwd), connection_string TSRMLS_CC);
}
}
}
@ -1172,3 +1196,56 @@ size_t core_str_zval_is_true( _Inout_ zval* value_z )
return 0; // false
}
void access_token_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC )
{
SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "An access token must be a byte string.");
size_t value_len = Z_STRLEN_P(value);
CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN) {
throw core::CoreException();
}
const char* value_str = Z_STRVAL_P( value );
// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the
// SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure
//
// typedef struct AccessToken
// {
// unsigned int dataSize;
// char data[];
// } ACCESSTOKEN;
//
// NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows.
//
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte,
// similar to a UCS-2 string containing only ASCII characters
//
// See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token
size_t dataSize = 2 * value_len;
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> accToken;
accToken = reinterpret_cast<ACCESSTOKEN*>(sqlsrv_malloc(sizeof(ACCESSTOKEN) + dataSize));
ACCESSTOKEN *pAccToken = accToken.get();
SQLSRV_ASSERT(pAccToken != NULL, "Something went wrong when trying to allocate memory for the access token.");
pAccToken->dataSize = dataSize;
// Expand access token with padding bytes
for (size_t i = 0, j = 0; i < dataSize; i += 2, j++) {
pAccToken->data[i] = value_str[j];
pAccToken->data[i+1] = 0;
}
core::SQLSetConnectAttr(conn, SQL_COPT_SS_ACCESS_TOKEN, reinterpret_cast<SQLPOINTER>(pAccToken), SQL_IS_POINTER);
// Save the pointer because SQLDriverConnect() will use it to make connection to the server
conn->azure_ad_access_token = pAccToken;
accToken.transferred();
}

View file

@ -1076,6 +1076,8 @@ struct sqlsrv_conn : public sqlsrv_context {
col_encryption_option ce_option; // holds the details of what are required to enable column encryption
DRIVER_VERSION driver_version; // version of ODBC driver
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> azure_ad_access_token;
// initialize with default values
sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) :
sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding )
@ -1105,6 +1107,7 @@ enum SQLSRV_STMT_OPTIONS {
namespace ODBCConnOptions {
const char APP[] = "APP";
const char AccessToken[] = "AccessToken";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
@ -1140,6 +1143,7 @@ enum SQLSRV_CONN_OPTIONS {
SQLSRV_CONN_OPTION_INVALID,
SQLSRV_CONN_OPTION_APP,
SQLSRV_CONN_OPTION_ACCESS_TOKEN,
SQLSRV_CONN_OPTION_CHARACTERSET,
SQLSRV_CONN_OPTION_CONN_POOLING,
SQLSRV_CONN_OPTION_DATABASE,
@ -1222,14 +1226,14 @@ struct driver_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
struct ce_ksp_provider_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
struct ce_akv_str_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
struct access_token_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
// factory to create a connection (since they are subclassed to instantiate statements)
typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC );
@ -1718,6 +1722,8 @@ enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_AKV_SECRET_MISSING,
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN,
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
// Driver specific error codes starts from here.
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,

View file

@ -92,6 +92,7 @@
#define SQL_COPT_SS_TRUSTEDCMKPATHS (SQL_COPT_SS_BASE_EX+13)// List of trusted CMK paths
#define SQL_COPT_SS_CEKCACHETTL (SQL_COPT_SS_BASE_EX+14)// Symmetric Key Cache TTL
#define SQL_COPT_SS_AUTHENTICATION (SQL_COPT_SS_BASE_EX+15)// The authentication method used for the connection
#define SQL_COPT_SS_ACCESS_TOKEN (SQL_COPT_SS_BASE_EX+16)// The authentication access token used for the connection
// SQLColAttributes driver specific defines.
// SQLSetDescField/SQLGetDescField driver specific defines.
@ -370,6 +371,12 @@
#pragma warning(disable:4200)
#endif
typedef struct AccessToken
{
unsigned int dataSize;
char data[];
} ACCESSTOKEN;
// Keystore Provider interface definition
typedef struct CEKeystoreContext
{

View file

@ -180,6 +180,7 @@ namespace SSConnOptionNames {
// most of these strings are the same for both the sqlsrv_connect connection option
// and the name put into the connection string. MARS is the only one that's different.
const char APP[] = "APP";
const char AccessToken[] = "AccessToken";
const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
@ -257,6 +258,15 @@ const connection_option SS_CONN_OPTS[] = {
CONN_ATTR_STRING,
conn_str_append_func::func
},
{
SSConnOptionNames::AccessToken,
sizeof( SSConnOptionNames::AccessToken ),
SQLSRV_CONN_OPTION_ACCESS_TOKEN,
ODBCConnOptions::AccessToken,
sizeof( ODBCConnOptions::AccessToken),
CONN_ATTR_STRING,
access_token_set_func::func
},
{
SSConnOptionNames::ApplicationIntent,
sizeof( SSConnOptionNames::ApplicationIntent ),

View file

@ -420,6 +420,14 @@ ss_error SS_ERRORS[] = {
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -114, false}
},
{
SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.", -115, false}
},
{
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, false}
},
// terminate the list of errors/warnings
{ UINT_MAX, {} }

View file

@ -0,0 +1,3 @@
<?php
$accToken = 'TARGET_ACCESS_TOKEN';
?>

View file

@ -0,0 +1,157 @@
--TEST--
Test some basics of Azure AD Access Token support
--DESCRIPTION--
This test also expects certain exceptions to be thrown under some conditions.
--SKIPIF--
<?php require('skipif.inc');
require('skipif_azure_ad_acess_token.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
function verifyErrorMessage($exception, $expectedError, $msg)
{
if (strpos($exception->getMessage(), $expectedError) === false) {
echo "AzureAD access token test: expected to fail with $msg\n";
print_r($exception->getMessage());
echo "\n";
}
}
function connectWithEmptyAccessToken($server)
{
$dummyToken = '';
$expectedError = 'The Azure AD Access Token is empty. Expected a byte string.';
$connectionInfo = "AccessToken = $dummyToken;";
$testCase = 'empty token';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo");
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
}
function connectWithInvalidOptions($server)
{
$dummyToken = 'abcde';
$expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.';
$message = 'AzureAD access token test: expected to fail with ';
$uid = '';
$connectionInfo = "AccessToken = $dummyToken;";
$testCase = 'empty UID provided';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo", $uid);
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
$pwd = '';
$connectionInfo = "AccessToken = $dummyToken;";
$testCase = 'empty PWD provided';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd);
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
$uid = 'uid';
$connectionInfo = "AccessToken = $dummyToken;";
$testCase = 'UID provided';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo", $uid);
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
$pwd = '';
$connectionInfo = "AccessToken = $dummyToken;";
$testCase = 'PWD provided';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd);
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
$connectionInfo = "Authentication = SqlPassword; AccessToken = $dummyToken;";
$testCase = 'Authentication keyword';
try {
$conn = new PDO("sqlsrv:server = $server; $connectionInfo");
echo $message . $testCase . PHP_EOL;
} catch(PDOException $e) {
verifyErrorMessage($e, $expectedError, $testCase);
}
unset($connectionInfo);
}
function simpleTest($conn)
{
// Create table
$tableName = 'Simple';
$col1 = 'Some simple string value';
dropTable($conn, $tableName);
$query = "CREATE TABLE $tableName(ID INT IDENTITY(1,1), COL1 VARCHAR(25))";
$stmt = $conn->query($query);
// Insert one row
$query = "INSERT INTO $tableName VALUES ('$col1')";
$stmt = $conn->query($query);
// Fetch data
$query = "SELECT * FROM $tableName";
$stmt = $conn->query($query);
$result = $stmt->fetch(PDO::FETCH_NUM);
$id = $result[0];
if ($id != 1) {
echo "AzureAD access token test: fetched id $id unexpected\n";
}
$field = $result[1];
if ($field !== $col1) {
echo "AzureAD access token test: fetched value $field unexpected\n";
}
dropTable($conn, $tableName);
}
// First test some error conditions
require_once('MsSetup.inc');
connectWithInvalidOptions($server);
// Then, test with an empty access token
connectWithEmptyAccessToken($server);
// Next, test with a valid access token and perform some simple tasks
require_once('access_token.inc');
try {
if ($adServer != 'TARGET_AD_SERVER' && $accToken != 'TARGET_ACCESS_TOKEN') {
$connectionInfo = "Database = $adDatabase; AccessToken = $accToken;";
$conn = new PDO("sqlsrv:server = $adServer; $connectionInfo");
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true);
simpleTest($conn);
unset($conn);
}
} catch(PDOException $e) {
print_r( $e->getMessage() );
echo PHP_EOL;
}
echo "Done\n";
?>
--EXPECT--
Done

View file

@ -0,0 +1,41 @@
<?php
// Check if the Azure AD Access Token test should be skipped
// From online documentation - For the ODBC Driver version 13.1,
// the Azure Active Directory access token authentication is Windows only.
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
require_once("MsSetup.inc");
$conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd);
if ($conn === false) {
die("skip Could not connect during SKIPIF.");
}
$msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"];
$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0];
$isWin = (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN');
if (!$isWin && $msodbcsqlMaj < 17) {
die("skip: Unsupported ODBC driver version");
}
// Now check SQL Server version - exclude this check if running on Azure
if (!$daasMode) {
$stmt = $conn->query("SELECT @@VERSION");
if ($stmt) {
$ver_string = $stmt->fetch(PDO::FETCH_NUM)[0];
} else {
die("skip Could not fetch SQL Server version during SKIPIF.");
}
$version = explode(' ', $ver_string);
if ($version[3] < '2016') {
die("skip: Wrong version of SQL Server, 2016 or later required");
}
}
?>

View file

@ -7,11 +7,11 @@ if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' );
$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN');
require_once( "MsSetup.inc" );
require_once("MsSetup.inc");
$conn = new PDO( "sqlsrv:server = $server ;", $uid, $pwd );
$conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd);
if ($conn === false) {
die( "skip Could not connect during SKIPIF." );
}

View file

@ -0,0 +1,3 @@
<?php
$accToken = 'TARGET_ACCESS_TOKEN';
?>

View file

@ -0,0 +1,45 @@
<?php
// Check if the Azure AD Access Token test should be skipped
// From online documentation - For the ODBC Driver version 13.1,
// the Azure Active Directory access token authentication is Windows only.
if (!extension_loaded("sqlsrv")) {
die("skip Extension not loaded");
}
require_once("MsSetup.inc");
$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver);
$conn = sqlsrv_connect($server, $connectionInfo);
if ($conn === false) {
die("skip: Could not connect during SKIPIF.");
}
$msodbcsqlVer = sqlsrv_client_info($conn)['DriverVer'];
$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0];
$isWin = (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN');
if (!$isWin && $msodbcsqlMaj < 17) {
die("skip: Unsupported ODBC driver version");
}
// Now check SQL Server version - exclude this check if running on Azure
if (!$daasMode) {
// Get SQL Server version
$stmt = sqlsrv_query($conn, "SELECT @@VERSION");
if (sqlsrv_fetch($stmt)) {
$verString = sqlsrv_get_field($stmt, 0);
} else {
die("skip Could not fetch SQL Server version.");
}
$version = explode(' ', $verString);
if ($version[3] < '2016') {
die("skip: Wrong version of SQL Server, 2016 or later required");
}
}
?>

View file

@ -7,11 +7,11 @@ if (!extension_loaded("sqlsrv")) {
die("skip Extension not loaded");
}
$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' );
$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN');
require_once( "MsSetup.inc" );
require_once("MsSetup.inc");
$connectionInfo = array( "UID"=>$userName, "PWD"=>$userPassword );
$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver);
$conn = sqlsrv_connect( $server, $connectionInfo );
if ($conn === false) {

View file

@ -0,0 +1,131 @@
--TEST--
Test some basics of Azure AD Access Token support
--DESCRIPTION--
This test also expects certain exceptions to be thrown under some conditions.
--SKIPIF--
<?php require('skipif.inc');
require('skipif_azure_ad_acess_token.inc'); ?>
--FILE--
<?php
require_once("MsCommon.inc");
function verifyErrorMessage($conn, $expectedError, $msg)
{
if ($conn === false) {
if (strpos(sqlsrv_errors($conn)[0]['message'], $expectedError) === false) {
print_r(sqlsrv_errors());
}
} else {
fatalError("AzureAD access token test: expected to fail with $msg\n");
}
}
function connectWithEmptyAccessToken($server)
{
$dummyToken = '';
$expectedError = 'The Azure AD Access Token is empty. Expected a byte string.';
$connectionInfo = array("AccessToken" => "$dummyToken");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'empty token');
unset($connectionInfo);
}
function connectWithInvalidOptions($server)
{
$dummyToken = 'abcde';
$expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.';
$connectionInfo = array("UID"=>"", "AccessToken" => "$dummyToken");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'empty UID provided');
unset($connectionInfo);
$connectionInfo = array("PWD"=>"", "AccessToken" => "$dummyToken");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'empty PWD provided');
unset($connectionInfo);
$connectionInfo = array("UID"=>"uid", "AccessToken" => "$dummyToken");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'UID provided');
unset($connectionInfo);
$connectionInfo = array("PWD"=>"pwd", "AccessToken" => "$dummyToken");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'PWD provided');
unset($connectionInfo);
$connectionInfo = array("Authentication"=>"SqlPassword", "AccessToken" => "$dummyToken");
$conn = sqlsrv_connect($server, $connectionInfo);
verifyErrorMessage($conn, $expectedError, 'Authentication keyword');
unset($connectionInfo);
}
function simpleTest($conn)
{
// Create table
$tableName = 'Simple';
$col1 = 'Some simple string value';
dropTable($conn, $tableName);
$query = "CREATE TABLE $tableName(ID INT IDENTITY(1,1), COL1 VARCHAR(25))";
$stmt = sqlsrv_query($conn, $query);
if (!$stmt) {
fatalError("AzureAD access token test: failed to create a table\n");
}
// Insert one row
$query = "INSERT INTO $tableName VALUES ('$col1')";
$stmt = sqlsrv_query($conn, $query);
if (!$stmt) {
fatalError("AzureAD access token test: failed to insert a row\n");
}
// Fetch data
$query = "SELECT * FROM $tableName";
$stmt = sqlsrv_query($conn, $query);
if (!$stmt) {
fatalError("AzureAD access token test: failed to fetch a table\n");
}
while (sqlsrv_fetch($stmt)) {
$id = sqlsrv_get_field($stmt, 0);
if ($id != 1) {
fatalError("AzureAD access token test: fetched id $id unexpected\n");
}
$field = sqlsrv_get_field($stmt, 1);
if ($field !== $col1) {
fatalError("AzureAD access token test: fetched value $field unexpected\n");
}
}
dropTable($conn, $tableName);
}
// First test some error conditions
connectWithInvalidOptions($server);
// Then, test with an empty access token
connectWithEmptyAccessToken($server);
// Next, test with a valid access token and perform some simple tasks
require_once('access_token.inc');
if ($adServer != 'TARGET_AD_SERVER' && $accToken != 'TARGET_ACCESS_TOKEN') {
$connectionInfo = array("Database"=>$adDatabase, "AccessToken"=>$accToken);
$conn = sqlsrv_connect($adServer, $connectionInfo);
if ($conn === false) {
fatalError("Could not connect with Azure AD AccessToken.\n");
} else {
simpleTest($conn);
sqlsrv_close($conn);
}
}
echo "Done\n";
?>
--EXPECT--
Done