Issues (1752)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

tests/FwlibTest/Db/SyncDbDataMysqlTest.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace FwlibTest\Db;
3
4
use Fwlib\Db\SyncDbData;
5
use Fwlib\Test\AbstractDbRelateTest;
6
use Fwlib\Util\UtilContainer;
7
8
/**
9
 * @copyright   Copyright 2013-2015 Fwolf
10
 * @license     http://www.gnu.org/licenses/lgpl.html LGPL-3.0+
11
 */
12
class SyncDbDataMysqlTest extends AbstractDbRelateTest
13
{
14
    public static $flock = true;
15
    public static $fopen = null;
16
    public static $unlink = null;
17
18
    public static $dbUsing = 'mysql';
19
    public static $tableUserDest = 'test_user_dest';
20
21
    /** @var UtilContainer */
22
    protected static $utilContainer;
23
24
    /**
25
     * Rows of initial test data
26
     */
27
    private $totalRows = 16;
28
29
30
    protected static function generateUuid()
31
    {
32
        return self::$utilContainer->getUuidBase36()->generate();
33
    }
34
35
36
    private static function removeDestTable()
37
    {
38
        if (self::$dbMysql->isTableExist(self::$tableUserDest)) {
39
            self::$dbMysql->Execute('DROP TABLE ' . self::$tableUserDest);
40
        }
41
    }
42
43
44
    public static function setUpBeforeClass()
45
    {
46
        parent::setUpBeforeClass();
47
48
        self::removeDestTable();
49
50
        self::$utilContainer = UtilContainer::getInstance();
51
    }
52
53
54
    public static function tearDownAfterClass()
55
    {
56
        parent::tearDownAfterClass();
57
58
        // Delete record table
59
        self::$flock = true;
60
        $sdd = new SyncDbData();
61
        self::$dbMysql->Execute("DROP TABLE {$sdd->tableRecord}");
62
63
        self::removeDestTable();
64
    }
65
66
67
    public function testLock()
68
    {
69
        // Normal lock file release
70
        $sdd = new SyncDbData();
71
        $y = self::$fopen;
72
        unset($sdd);
73
        $this->assertEquals($y, self::$unlink);
74
    }
75
76
77
    public function testSetDb()
78
    {
79
        $sdd = new SyncDbData();
80
        $sdd->setDb(self::$dbMysql, self::$dbMysql->profile);
81
82
        $this->assertTrue(self::$dbMysql->isTableExist($sdd->tableRecord));
83
84
        // Create again and check record table exists
85
        $sdd = new syncDbData();
86
        $sdd->setDb(self::$dbMysql, self::$dbMysql);
87
        $this->assertEquals(
88
            "Record table {$sdd->tableRecord} already exists.",
89
            array_pop($sdd->logMessage)
90
        );
91
    }
92
93
94
    /**
95
     * Need before testSyncOneWay(), which create timestamp column
96
     *
97
     * @expectedException Exception
98
     * @expectedExceptionMessage Table test_user in source db hasn't timestamp column.
99
     */
100
    public function testSyncOneWayWithSourceHasNoTimestampColumn()
101
    {
102
        $sdd = new SyncDbData;
103
        $sdd->setDb(self::$dbMysql, self::$dbMysql);
104
        $config = [
105
            self::$tableUser => self::$tableUserDest,
106
        ];
107
        $sdd->syncOneWay($config);
108
    }
109
110
111
    public function testSyncOneWay()
112
    {
113
        $tableUser = self::$tableUser;
114
        $tableUserDest = self::$tableUserDest;
115
        $tableNotExist = 'test_not_exist';
116
117
118
        // Add timestamp column in source db
119
        self::$dbMysql->Execute(
120
            "ALTER TABLE {$tableUser}
121
                ADD COLUMN ts TIMESTAMP NOT NULL
122
                    DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
123
            ;"
124
        );
125
        // Refresh cached meta data
126
        self::$dbMysql->getMetaColumn($tableUser, true);
127
128
129
        // Create another test user table as sync dest
130
        self::$dbMysql->Execute(
131
            "CREATE TABLE {$tableUserDest} LIKE {$tableUser};"
132
        );
133
134
135
        // Prepare dummy date in source table
136
        $data = [];
137
        for ($i = 0; $i < $this->totalRows; $i ++) {
138
            $data[] = [
139
                'uuid'  => self::generateUuid(),
140
                'title' => "Title - $i",
141
                'age'   => $i + 20,
142
                'credit'    => $i * 100,
143
            ];
144
        }
145
        self::$dbMysql->write($tableUser, $data, 'I');
146
        $rowsSource = self::$dbMysql->getRowCount($tableUser);
0 ignored issues
show
$rowsSource is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
147
148
149
        // Prepare SyncDbData instance
150
        // The table $tableNotExist if test dummy, we will use mock to create
151
        // convertDataXxx() method for it, and return empty convert result to
152
        // skip data write to it.
153
        $config = [
154
            $tableUser => [$tableUserDest, $tableNotExist],
155
        ];
156
157
        $stringUtil = self::$utilContainer->getString();
158
159
        // Mock instance with 2 additional convert method
160
        $convertForNotExist = 'convertData' .
161
            $stringUtil->toStudlyCaps($tableUser) .
162
            'To' . $stringUtil->toStudlyCaps($tableNotExist);
163
        $convertForUserDest = 'convertData' .
164
            $stringUtil->toStudlyCaps($tableUser) .
165
            'To' . $stringUtil->toStudlyCaps($tableUserDest);
166
        $sdd = $this->getMock(
167
            SyncDbData::class,
168
            [$convertForNotExist]
169
        );
170
        $sdd->expects($this->any())
171
            ->method($convertForNotExist)
172
            ->will($this->returnValue(null));
173
174
        $sdd->setDb(self::$dbMysql, self::$dbMysql);
175
        $sdd->batchSize = 10;
176
177
178
        // First sync round, limit by batchSize
179
180
        $this->assertEquals($sdd->batchSize, $sdd->syncOneway($config));
181
        $this->assertEquals(
182
            $sdd->batchSize,
183
            self::$dbMysql->getRowCount($tableUserDest)
184
        );
185
186
        // Run sync again will sync nothing, because batchSize limit
187
        $this->assertEquals(0, $sdd->syncOneWay($config));
188
189
190
        // Second sync round, full sync, not reach batchSize limit
191
192
        $sdd = $this->getMock(
193
            SyncDbData::class,
194
            [$convertForNotExist, $convertForUserDest]
195
        );
196
        $sdd->expects($this->any())
197
            ->method($convertForNotExist)
198
            ->will($this->returnValue(null));
199
200
        // Change age column through convert data method
201
        $callback = function ($sourceAr) {
202
            $sourceAr['age'] = 42;
203
            return $sourceAr;
204
        };
205
        $sdd->expects($this->any())
206
            ->method($convertForUserDest)
207
            ->will($this->returnCallback($callback));
208
209
        $sdd->setDb(self::$dbMysql, self::$dbMysql);
210
        // Mysql timestamp is not unique, so we need raise batchSize to sync
211
        // all rows. It need not clear record table.
212
        $sdd->batchSize = 200;
213
        //self::$dbMysql->Execute('TRUNCATE TABLE ' . self::$tableUserDest);
214
215
        $this->assertEquals($this->totalRows, $sdd->syncOneWay($config));
216
        $this->assertEquals(
217
            $this->totalRows,
218
            self::$dbMysql->getRowCount($tableUserDest)
219
        );
220
221
        // In dest db, column age in all rows are same value return by
222
        // callback function we defined, assert it now.
223
        $rs = self::$dbMysql->execute(
224
            "SELECT DISTINCT age from $tableUserDest"
225
        );
226
        $this->assertEquals(1, $rs->RowCount());
227
        $this->assertEquals(42, $rs->fields['age']);
228
    }
229
230
231
    /**
232
     * Need after testSyncOneWay()
233
     */
234
    public function testSyncDelete()
235
    {
236
        $tableUser = self::$tableUser;
237
        $tableUserDest = self::$tableUserDest;
238
239
240
        // Prepare SyncDbData instance
241
        // The 2nd $tableUserDest is test dummy for empty table or table need
242
        // not to sync. All actual sync is done on 1st $tableUserDest.
243
        $config = [
244
            $tableUser => [$tableUserDest, $tableUserDest],
245
        ];
246
247
        $stringUtil = self::$utilContainer->getString();
248
249
        // Mock instance with 2 additional convert method
250
        $compareForUserDest = 'compareData' .
251
            $stringUtil->toStudlyCaps($tableUser) .
252
            'To' . $stringUtil->toStudlyCaps($tableUserDest);
253
        $sdd = $this->getMock(
254
            SyncDbData::class,
255
            [$compareForUserDest]
256
        );
257
        $db = self::$dbMysql;
258
        $callback = function () use ($db, $tableUser, $tableUserDest) {
259
            $countSource = $db->getRowCount($tableUser);
260
            $countDest = $db->getRowCount($tableUserDest);
261
262
            if (1 >= ($countDest - $countSource)) {
263
                return null;
264
265
            } else {
266
                $rs = $db->SelectLimit(
0 ignored issues
show
Documentation Bug introduced by
The method SelectLimit does not exist on object<Fwlib\Bridge\Adodb>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
267
                    "SELECT uuid FROM $tableUserDest",
268
                    $countDest - $countSource
269
                );
270
                return $rs->GetArray();
271
            }
272
        };
273
        $sdd->expects($this->any())
274
            ->method($compareForUserDest)
275
            ->will($this->returnCallback($callback));
276
277
        $sdd->setDb(self::$dbMysql, self::$dbMysql);
278
279
280
        // If previous reach batchSize, will skip sync
281
        $sdd->batchSize = 0;
282
        $this->assertEquals(0, $sdd->syncDelete($config));
283
284
285
        $sdd->batchSize = 10;
286
287
        // First sync round, db rows count diff but still no rows need to be delete
288
        self::$dbMysql->Execute("DELETE FROM $tableUser LIMIT 1");
289
        $this->assertEquals(0, $sdd->syncDelete($config));
290
291
292
        // Second sync round, only 2 row need to delete
293
        self::$dbMysql->Execute("DELETE FROM $tableUser LIMIT 1");
294
        $this->assertEquals(2, $sdd->syncDelete($config));
295
296
297
        // Third sync round, all rest need sync but limit by batchSize
298
        // Remove 2nd $tableUserDest in $config, because do sync on it will
299
        // exceed batchSize.
300
        $config = [
301
            $tableUser => [$tableUserDest],
302
            'not_exist' => 'not_exist',
303
        ];
304
305
        self::$dbMysql->Execute("TRUNCATE TABLE $tableUser");
306
        $this->assertEquals($sdd->batchSize - 2, $sdd->syncDelete($config));
307
308
        $this->assertEquals(
309
            $this->totalRows - $sdd->batchSize,
310
            self::$dbMysql->getRowCount($tableUserDest)
311
        );
312
313
        // Sync for not_exist is skipped
314
        $this->assertEquals(
315
            "Reach batchSize limit {$sdd->batchSize}.",
316
            $sdd->logMessage[count($sdd->logMessage) - 2]
317
        );
318
    }
319
320
321
    /**
322
     * @expectedException Exception
323
     * @expectedExceptionMessage Compare method needed:
324
     */
325
    public function testSyncDeleteWithoutCompareMethod()
326
    {
327
        $tableUser = self::$tableUser;
328
        $tableUserDest = self::$tableUserDest;
329
330
        $config = [
331
            $tableUser => [$tableUserDest],
332
        ];
333
334
        $sdd = new SyncDbData;
335
        $sdd->setDb(self::$dbMysql, self::$dbMysql);
336
337
        $sdd->syncDelete($config);
338
    }
339
340
341
    /**
342
     * Put last avoid influence other tests
343
     *
344
     * @expectedException Exception
345
     * @expectedExceptionMessage Aborted: Lock file check failed.
346
     */
347
    public function testLockException()
348
    {
349
        // Duplicate instance will throw exception
350
        self::$flock = false;
351
        $sdd = new SyncDbData();
0 ignored issues
show
$sdd is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
352
    }
353
}
354
355
356
// Overwrite build-in function for test
357
358
namespace Fwlib\Db;
359
360
function fclose($fileHandle)
0 ignored issues
show
The parameter $fileHandle is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
361
{
362
}
363
364
function flock($fileHandle, $mode)
0 ignored issues
show
The parameter $fileHandle is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $mode is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
365
{
366
    return \FwlibTest\Db\SyncDbDataMysqlTest::$flock;
367
}
368
369
function fopen($path)
370
{
371
    \FwlibTest\Db\SyncDbDataMysqlTest::$fopen = $path;
372
    return $path;
373
}
374
375
function unlink($path)
376
{
377
    \FwlibTest\Db\SyncDbDataMysqlTest::$unlink = $path;
378
}
379