1
0
mirror of https://github.com/e107inc/e107.git synced 2025-08-01 20:30:39 +02:00

e_db_pdo::copyRow() - Greatly decrease collision chance

Increased possible random strings for unique fields in e_db_pdo::copyRow() from 1000 to 59^11 (UserHandler::generateRandomString() "alphanumeric" should have 59 characters to choose from)

If a collision still happens, e_db_pdo::copyRow() retries up to 3 times for a successful copy.

Fixes: #3678
This commit is contained in:
Nick Liu
2019-12-27 21:54:11 +01:00
parent 5e9b3bee14
commit fabb0c2757
2 changed files with 82 additions and 28 deletions

View File

@@ -2295,42 +2295,47 @@ class e_db_pdo implements e_db
return false;
}
if($fields === '*')
{
$fields = $this->db_FieldList($table);
$unique = $this->_getUnique($table);
for ($retries = 0; $retries < 3; $retries ++) {
if ($fields === '*') {
$fieldList = $this->db_FieldList($table);
$unique = $this->_getUnique($table);
$flds = array();
// randomize fields that must be unique.
foreach($fields as $fld)
{
if(isset($unique[$fld]))
{
$flds[] = $unique[$fld] === 'PRIMARY' ? 0 : "'rand-".rand(0,999)."'"; // keep it short.
continue;
$flds = array();
// randomize fields that must be unique.
foreach ($fieldList as $fld) {
if (isset($unique[$fld])) {
$flds[] = $unique[$fld] === 'PRIMARY' ? 0 :
"'rand-" . e107::getUserSession()->generateRandomString('***********') . "'";
continue;
}
$flds[] = $fld;
}
$flds[] = $fld;
$fieldList = implode(",", $fieldList);
$fieldList2 = implode(",", $flds);
} else {
$fieldList = $fields;
$fieldList2 = $fieldList;
}
$fieldList = implode(",", $fields);
$fieldList2 = implode(",", $flds);
}
else
{
$fieldList = $fields;
$fieldList2 = $fieldList;
}
if (empty($fieldList)) {
$this->mysqlLastErrText = "copyRow \$fields list was empty";
return false;
}
if(empty($fieldList))
{
$this->mysqlLastErrText = "copyRow \$fields list was empty";
return false;
$beforeLastInsertId = $this->lastInsertId();
$id = $this->gen(
"INSERT INTO " . $this->mySQLPrefix . $table .
"(" . $fieldList . ") SELECT " .
$fieldList2 .
" FROM " . $this->mySQLPrefix . $table .
" WHERE " . $args
);
$lastInsertId = $this->lastInsertId();
if ($beforeLastInsertId !== $lastInsertId) break;
}
$id = $this->gen("INSERT INTO ".$this->mySQLPrefix.$table."(".$fieldList.") SELECT ".$fieldList2." FROM ".$this->mySQLPrefix.$table." WHERE ".$args);
$lastInsertId = $this->lastInsertId();
return ($id && $lastInsertId) ? $lastInsertId : false;
}

View File

@@ -118,4 +118,53 @@ class e_db_pdoTest extends e_db_abstractTest
$qry = $this->db->getLastErrorText();
$this->assertGreaterThan(1,$result, $qry);
}
public function test_Db_CopyRowRNGRetry()
{
$original_user_handler = e107::getRegistry('core/e107/singleton/UserHandler');
$evil_user_handler = $this->make('UserHandler', [
'generateRandomString' => function($pattern = '', $seed = '')
{
static $index = 0;
$mock_values = ['same0000000', 'same0000000', 'different00'];
return $mock_values[$index ++];
}
]);
e107::setRegistry('core/e107/singleton/UserHandler', $evil_user_handler);
// test with table that has unique keys.
$result = $this->db->db_CopyRow('core_media_cat', '*', "media_cat_id = 1");
$qry = $this->db->getLastErrorText();
$this->assertGreaterThan(1,$result, $qry);
// test with table that has unique keys. (same row again) - make sure copyRow duplicates it regardless.
$result = $this->db->db_CopyRow('core_media_cat', '*', "media_cat_id = 1");
$qry = $this->db->getLastErrorText();
$this->assertGreaterThan(1,$result, $qry);
e107::setRegistry('core/e107/singleton/UserHandler', $original_user_handler);
}
public function test_Db_CopyRowRNGGiveUp()
{
$original_user_handler = e107::getRegistry('core/e107/singleton/UserHandler');
$evil_user_handler = $this->make('UserHandler', [
'generateRandomString' => function($pattern = '', $seed = '')
{
return 'neverchange';
}
]);
e107::setRegistry('core/e107/singleton/UserHandler', $evil_user_handler);
// test with table that has unique keys.
$result = $this->db->db_CopyRow('core_media_cat', '*', "media_cat_id = 1");
$result = $this->db->db_CopyRow('core_media_cat', '*', "media_cat_id = 1");
$qry = $this->db->getLastErrorText();
$this->assertFalse($result,
"Intentionally broken random number generator should have prevented row copy with unique keys"
);
e107::setRegistry('core/e107/singleton/UserHandler', $original_user_handler);
}
}