Added insertion tests for encrypted smalldatetime and datetime columns (#1174)

This commit is contained in:
Jenny Tam 2020-08-07 09:42:57 -07:00 committed by GitHub
parent 3dddbbb2e2
commit 9365fc5201
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 296 additions and 4 deletions

View file

@ -748,10 +748,14 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
core::SQLBindParameter( stmt, param_num + 1, direction,
c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr );
if ( stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP )
{
if( decimal_digits == 3 )
core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER );
// When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively.
// For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error.
// This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns.
// To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column.
if (stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP) {
if (decimal_digits == 3)
core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER);
else if (decimal_digits == 0)
core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER );
}

View file

@ -0,0 +1,139 @@
--TEST--
Test for inserting and retrieving encrypted data of datetime and smalldatetime types encrypted
--DESCRIPTION--
Verify that inserting into smalldatetime column might trigger "Datetime field overflow" error
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
function getColDef($name, $type)
{
$append = " ENCRYPTED WITH (ENCRYPTION_TYPE = deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AEColumnKey) ";
$colDef = '[' . $name . '] ' . $type . $append;
return $colDef;
}
function createTableEncryptedQuery($conn, $tableName, $columns)
{
$tsql = "CREATE TABLE $tableName (";
foreach ($columns as $name => $type) {
$colDef = getColDef($name, $type) . ', ';
$tsql .= $colDef;
}
$tsql = rtrim($tsql, ', ') . ')';
return $tsql;
}
function createTablePlainQuery($conn, $tableName, $columns)
{
$tsql = "CREATE TABLE $tableName (";
foreach ($columns as $name => $type) {
$colDef = '[' . $name . '] ' . $type . ', ';
$tsql .= $colDef;
}
$tsql = rtrim($tsql, ', ') . ')';
return $tsql;
}
try {
// This test requires to connect with the Always Encrypted feature
// First check if the system is qualified to run this test
$dsn = getDSN($server, null);
$conn = new PDO($dsn, $uid, $pwd);
$qualified = isAEQualified($conn) && (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
if ($qualified) {
unset($conn);
// Now connect with ColumnEncryption enabled
$connectionInfo = "ColumnEncryption = Enabled;";
$conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd);
}
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$tableName = 'pdo_datetime_encrypted';
dropTable($conn, $tableName);
// Define the column definitions
$columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(4)');
if ($qualified) {
$tsql = createTableEncryptedQuery($conn, $tableName, $columns);
} else {
$tsql = createTablePlainQuery($conn, $tableName, $columns);
}
$conn->exec($tsql);
// Insert values that cause errors
$val1 = '9999-12-31 23:59:59';
$val2 = null;
$val3 = '9999-12-31 23:59:59.9999';
$tsql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?,?,?)";
$stmt = $conn->prepare($tsql);
$stmt->bindParam(1, $val1);
$stmt->bindParam(2, $val2);
$stmt->bindParam(3, $val3);
try {
$stmt->execute();
} catch (PDOException $e) {
$error = ($qualified)? '*Datetime field overflow' : '*The conversion of a nvarchar data type to a smalldatetime data type resulted in an out-of-range value.';
if (!fnmatch($error, $e->getMessage())) {
echo "The error message is unexpected:\n";
var_dump($e->getMessage());
}
}
// These values should work
$val1 = '2021-11-03 11:49:00';
$val2 = '2015-10-23 07:03:00.000';
try {
$stmt->execute();
} catch (PDOException $e) {
echo "Errors unexpected!!\n";
var_dump($e->getMessage());
}
unset($stmt);
// Now fetch the values
$tsql = "SELECT * FROM $tableName";
$stmt = $conn->prepare($tsql);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_NUM);
var_dump($row);
dropTable($conn, $tableName);
unset($stmt);
unset($conn);
} catch (PDOException $e) {
echo $e->getMessage();
}
echo "Done\n";
?>
--EXPECT--
array(3) {
[0]=>
string(19) "2021-11-03 11:49:00"
[1]=>
string(23) "2015-10-23 07:03:00.000"
[2]=>
string(24) "9999-12-31 23:59:59.9999"
}
Done

View file

@ -0,0 +1,149 @@
--TEST--
Test for inserting and retrieving encrypted data of datetime and smalldatetime types encrypted
--DESCRIPTION--
Verify that inserting into smalldatetime column might trigger "Datetime field overflow" error
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
function getColDef($name, $type)
{
$append = " ENCRYPTED WITH (ENCRYPTION_TYPE = deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AEColumnKey) ";
$colDef = '[' . $name . '] ' . $type . $append;
return $colDef;
}
function createTableEncryptedQuery($conn, $tableName, $columns)
{
$tsql = "CREATE TABLE $tableName (";
foreach ($columns as $name => $type) {
$colDef = getColDef($name, $type) . ', ';
$tsql .= $colDef;
}
$tsql = rtrim($tsql, ', ') . ')';
return $tsql;
}
function createTablePlainQuery($conn, $tableName, $columns)
{
$tsql = "CREATE TABLE $tableName (";
foreach ($columns as $name => $type) {
$colDef = '[' . $name . '] ' . $type . ', ';
$tsql .= $colDef;
}
$tsql = rtrim($tsql, ', ') . ')';
return $tsql;
}
require_once("MsCommon.inc");
// This test requires to connect with the Always Encrypted feature
// First check if the system is qualified to run this test
$options = array('Database' => $database, 'UID' => $userName, 'PWD' => $userPassword, 'ReturnDatesAsStrings' => true);
$conn = sqlsrv_connect($server, $options);
if ($conn === false) {
fatalError("Failed to connect to $server.");
}
$qualified = AE\isQualified($conn) && (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
if ($qualified) {
sqlsrv_close($conn);
// Now connect with ColumnEncryption enabled
$connectionOptions = array_merge($options, array('ColumnEncryption' => 'Enabled'));
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false) {
fatalError("Failed to connect to $server.");
}
}
$tableName = 'srv_datetime_encrypted';
dropTable($conn, $tableName);
// Define the column definitions
$columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(4)');
if ($qualified) {
$tsql = createTableEncryptedQuery($conn, $tableName, $columns);
} else {
$tsql = createTablePlainQuery($conn, $tableName, $columns);
}
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
fatalError("Failed to create table $tableName\n");
}
// Insert values that cause errors
$val1 = '9999-12-31 23:59:59';
$val2 = null;
$val3 = '9999-12-31 23:59:59.9999';
$tsql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?,?,?)";
$params = array($val1, $val2, $val3);
$stmt = sqlsrv_prepare($conn, $tsql, $params);
if (!$stmt) {
fatalError("Failed to prepare insert statement");
}
$result = sqlsrv_execute($stmt);
if ($result) {
echo "Inserting invalid values should have failed!\n";
} else {
$error = ($qualified)? '*Datetime field overflow' : '*The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value.';
if (!fnmatch($error, sqlsrv_errors()[0]['message'])) {
var_dump(sqlsrv_errors());
}
}
sqlsrv_free_stmt($stmt);
// These values should work
$val1 = '2021-11-03 11:49:00';
$val2 = '2015-10-23 07:03:00.000';
$params = array($val1, $val2, $val3);
$stmt = sqlsrv_prepare($conn, $tsql, $params);
if (!$stmt) {
fatalError("Failed to prepare insert statement");
}
$result = sqlsrv_execute($stmt);
if (!$result) {
fatalError("Failed to insert valid values\n");
}
sqlsrv_free_stmt($stmt);
// Now fetch the values
$tsql = "SELECT * FROM $tableName";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
fatalError("Failed to select from $tableName");
}
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
var_dump($row);
dropTable($conn, $tableName);
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
echo "Done\n";
?>
--EXPECT--
array(3) {
["c1"]=>
string(19) "2021-11-03 11:49:00"
["c2"]=>
string(23) "2015-10-23 07:03:00.000"
["c3"]=>
string(24) "9999-12-31 23:59:59.9999"
}
Done