GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (35)

Security Analysis    no request data  

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.

src/statemachine/persistence/PDO.php (5 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 izzum\statemachine\persistence;
3
use izzum\statemachine\loader\Loader;
4
use izzum\statemachine\StateMachine;
5
use izzum\statemachine\Identifier;
6
use izzum\statemachine\loader\LoaderArray;
7
use izzum\statemachine\loader\LoaderData;
8
use izzum\statemachine\Exception;
9
use izzum\statemachine\Transition;
10
use izzum\statemachine\State;
11
12
/**
13
 * A persistence adapter/loader specifically for the PHP Data Objects (PDO)
14
 * extension.
15
 * PDO use database drivers for different backend implementations (postgres,
16
 * mysql, sqlite, MSSQL, oracle etc).
17
 * By providing a DSN connection string, you can connect to different backends.
18
 *
19
 * This specific adapter uses the schema as defined in
20
 * - /assets/sql/postgres.sql
21
 * - /assets/sql/sqlite.sql
22
 * - /assets/sql/mysql.sql
23
 * but can be used on tables for a different database vendor as long as
24
 * the table names, fields and constraints are the same. Optionally,
25
 * a prefix can be set.
26
 *
27
 * This Adapter does double duty as a Loader since both use the
28
 * same backend. You could use two seperate classes for this, but you can
29
 * implement both in one class. 
30
 * You could use the ReaderWriterDelegator to seperate
31
 * the reading of configuration and the persisting of state and transition data.
32
 * 
33
 *
34
 * If you need an adapter more specialized to your
35
 * needs/framework/system you can easily write one yourself.
36
 *
37
 * More functionality related to a backend can be implemented on this class eg:
38
 * - get the history of transitions from the history table
39
 * - get the factories for machines from the machine table
40
 *
41
 * @link http://php.net/manual/en/pdo.drivers.php
42
 * @link http://php.net/manual/en/book.pdo.php
43
 *      
44
 * @author Rolf Vreijdenberger
45
 */
46
class PDO extends Adapter implements Loader {
47
    
48
    /**
49
     * the pdo connection string
50
     * 
51
     * @var string
52
     */
53
    private $dsn;
54
    
55
    /**
56
     *
57
     * @var string
58
     */
59
    private $user;
60
    /**
61
     *
62
     * @var string
63
     */
64
    private $password;
65
    /**
66
     * pdo options
67
     * 
68
     * @var array
69
     */
70
    private $options;
71
    
72
    /**
73
     * the locally cached connections
74
     * 
75
     * @var \PDO
76
     */
77
    private $connection;
78
    
79
    /**
80
     * table prefix
81
     * 
82
     * @var string
83
     */
84
    private $prefix = '';
85
86
    /**
87
     *
88
     * @param string $dsn
89
     *            a PDO data source name
90
     *            example: 'pgsql:host=localhost;port=5432;dbname=izzum'
91
     * @param string $user
92
     *            optional, defaults to null
93
     * @param string $password
94
     *            optional, defaults to null
95
     * @param array $options
96
     *            optional, defaults to empty array.
97
     * @link http://php.net/manual/en/pdo.connections.php
98
     */
99
    public function __construct($dsn, $user = null, $password = null, $options = array())
100
    {
101
        $this->dsn = $dsn;
102
        $this->user = $user;
103
        $this->password = $password;
104
        $this->options = $options;
105
    }
106
107
    /**
108
     * get the connection to a database via the PDO adapter.
109
     * The connection retrieved will be reused if one already exists.
110
     * 
111
     * @return \PDO
112
     * @throws Exception
113
     */
114
    public function getConnection()
115
    {
116
        try {
117
            if ($this->connection === null) {
118
                $this->connection = new \PDO($this->dsn, $this->user, $this->password, $this->options);
119
                $this->setupConnection($this->connection);
120
            }
121
            return $this->connection;
122
        } catch(\Exception $e) {
123
            throw new Exception(sprintf("error creating PDO [%s], message: [%s]", $this->dsn, $e->getMessage()), Exception::PERSISTENCE_FAILED_TO_CONNECT);
124
        }
125
    }
126
    
127
    /**
128
     * set the PDO connection explicitely, useful if you want to share the
129
     * PDO instance when it is created outside this class. 
130
     * @param \PDO $connection
131
     */
132
    public function setConnection(\PDO $connection) 
133
    {
134
        $this->connection = $connection;
135
    }
136
137
    protected function setupConnection(\PDO $connection)
0 ignored issues
show
The parameter $connection 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...
138
    {
139
    /**
140
     * hook, override to:
141
     * - set schema on postgresql
142
     * - set PRAGMA on sqlite
143
     * - etc..
144
     * whatever is the need to do a setup on, the first time
145
     * you create a connection
146
     * - SET UTF-8 on mysql can be done with an option in the $options
147
     * constructor argument
148
     */
149
    }
150
151
    /**
152
     *
153
     * @return string the type of backend we connect to
154
     */
155
    public function getType()
156
    {
157
        $index = strpos($this->dsn, ":");
158
        return $index ? substr($this->dsn, 0, $index) : $this->dsn;
159
    }
160
161
    /**
162
     * set the table prefix to be used
163
     * 
164
     * @param string $prefix            
165
     */
166
    final public function setPrefix($prefix)
167
    {
168
        $this->prefix = $prefix;
169
    }
170
171
    /**
172
     * get the table prefix
173
     * 
174
     * @return string
175
     */
176
    final public function getPrefix()
177
    {
178
        return $this->prefix;
179
    }
180
    
181
    /**
182
     * {@inheritDoc}
183
     * This is an implemented method from the Loader interface.
184
     * All other methods are actually implemented methods from the Adapter
185
     * class.
186
     */
187
    public function load(StateMachine $statemachine)
188
    {
189
        $data = $this->getLoaderData($statemachine->getContext()->getMachine());
190
        // delegate to LoaderArray
191
        $loader = new LoaderArray($data);
192
        $loader->load($statemachine);
193
    }
194
195
    /**
196
     * {@inheritDoc}            
197
     */
198
    public function processGetState(Identifier $identifier)
199
    {
200
        $connection = $this->getConnection();
201
        $prefix = $this->getPrefix();
202
        try {
203
            $query = 'SELECT state FROM ' . $prefix . 'statemachine_entities WHERE machine = ' . ':machine AND entity_id = :entity_id';
204
            $machine = $identifier->getMachine();
205
            $entity_id = $identifier->getEntityId();
206
            $statement = $connection->prepare($query);
207
            $statement->bindParam(":machine", $machine);
208
            $statement->bindParam(":entity_id", $entity_id);
209
            $result = $statement->execute();
210
            if ($result === false) {
211
                throw new Exception($this->getErrorInfo($statement));
212
            }
213
        } catch(\Exception $e) {
214
            throw new Exception(sprintf('query for getting current state failed: [%s]', $e->getMessage()), Exception::PERSISTENCE_LAYER_EXCEPTION);
215
        }
216
        $row = $statement->fetch();
217
        if ($row === false) {
218
            throw new Exception(sprintf('no state found for [%s]. Did you "$machine->add()" it to the persistence layer?', $identifier->toString()), Exception::PERSISTENCE_LAYER_EXCEPTION);
219
            return State::STATE_UNKNOWN;
0 ignored issues
show
return \izzum\statemachine\State::STATE_UNKNOWN; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
220
        }
221
        return $row ['state'];
222
    }
223
224
225
    
226
    
227
    /**
228
     * {@inheritDoc}
229
     */
230
    public function getEntityIds($machine, $state = null)
231
    {
232
        $connection = $this->getConnection();
233
        $prefix = $this->getPrefix();
234
        $query = 'SELECT se.entity_id FROM ' . $prefix . 'statemachine_entities AS se
235
                JOIN ' . $prefix . 'statemachine_states AS ss ON (se.state = ss.state AND
236
                se.machine = ss.machine) WHERE se.machine = :machine';
237
        $output = array();
238
        try {
239
            if ($state != null) {
0 ignored issues
show
It seems like you are loosely comparing $state of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
240
                $query .= ' AND se.state = :state';
241
            }
242
            $statement = $connection->prepare($query);
243
            $statement->bindParam(":machine", $machine);
244
            if ($state != null) {
0 ignored issues
show
It seems like you are loosely comparing $state of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
245
                $statement->bindParam(":state", $state);
246
            }
247
    
248
            $result = $statement->execute();
249
            if ($result === false) {
250
                throw new Exception($this->getErrorInfo($statement));
251
            }
252
    
253
            $rows = $statement->fetchAll();
254
            if ($rows === false) {
255
                throw new Exception("failed getting rows: " . $this->getErrorInfo($statement));
256
            }
257
    
258
            foreach ($rows as $row) {
259
                $output [] = $row ['entity_id'];
260
            }
261
        } catch(\Exception $e) {
262
            throw new Exception($e->getMessage(), Exception::PERSISTENCE_LAYER_EXCEPTION, $e);
263
        }
264
        return $output;
265
    }
266
267
    /**
268
     * {@inheritDoc}
269
     */
270
    public function isPersisted(Identifier $identifier)
271
    {
272
        $connection = $this->getConnection();
273
        $prefix = $this->getPrefix();
274
        try {
275
            $query = 'SELECT entity_id FROM ' . $prefix . 'statemachine_entities WHERE ' . 'machine = :machine AND entity_id = :entity_id';
276
            $statement = $connection->prepare($query);
277
            $machine = $identifier->getMachine();
278
            $entity_id = $identifier->getEntityId();
279
            $statement->bindParam(":machine", $machine);
280
            $statement->bindParam(":entity_id", $entity_id);
281
            $result = $statement->execute();
282
            if ($result === false) {
283
                throw new Exception($this->getErrorInfo($statement));
284
            }
285
            
286
            $row = $statement->fetch();
287
            
288
            if ($row === false) {
289
                return false;
290
            }
291
            return ($row ['entity_id'] == $identifier->getEntityId());
292
        } catch(\Exception $e) {
293
            throw new Exception(sprintf('query for getting persistence info failed: [%s]', $e->getMessage()), Exception::PERSISTENCE_LAYER_EXCEPTION);
294
        }
295
    }
296
297
    /**
298
     * {@inheritDoc}           
299
     */
300
    protected function insertState(Identifier $identifier, $state, $message = null)
301
    {
302
        
303
        $connection = $this->getConnection();
304
        $prefix = $this->getPrefix();
305
        try {
306
            $query = 'INSERT INTO ' . $prefix . 'statemachine_entities
307
                (machine, entity_id, state, changetime)
308
                    VALUES
309
                (:machine, :entity_id, :state, :timestamp)';
310
            $statement = $connection->prepare($query);
311
            $machine = $identifier->getMachine();
312
            $entity_id = $identifier->getEntityId();
313
            $timestamp =  $this->getTimestampForDriver();
314
            $statement->bindParam(":machine", $machine);
315
            $statement->bindParam(":entity_id", $entity_id);
316
            $statement->bindParam(":state", $state);
317
            $statement->bindParam(":timestamp", $timestamp);
318
            $result = $statement->execute();
319
            if ($result === false) {
320
                throw new Exception($this->getErrorInfo($statement));
321
            }
322
        } catch(\Exception $e) {
323
            throw new Exception(sprintf('query for inserting state failed: [%s]', $e->getMessage()), Exception::PERSISTENCE_LAYER_EXCEPTION);
324
        }
325
    }
326
327
    /**
328
     * collects error information ready to be used as output.
329
     * @param \PDOStatement $statement
330
     * @return string
331
     */
332
    protected function getErrorInfo(\PDOStatement $statement)
333
    {
334
        $info = $statement->errorInfo();
335
        $output = sprintf("%s - message: '%s'", $info [0], $info [2]);
336
        return $output;
337
    }
338
339
    /**
340
     * hook method
341
     * 
342
     * @return number|string
343
     */
344
    protected function getTimestampForDriver()
345
    {
346
        // yuk, seems postgres and sqlite need some different input.
347
        // maybe other drivers too. so therefore this hook method.
348
        if (strstr($this->dsn, 'sqlite:')) {
349
            return time(); // "CURRENT_TIMESTAMP";//"DateTime('now')";
350
        }
351
        // might have to be overriden for certain drivers.
352
        return date("Y-m-d H:i:s", time()); // since prepared statements do not support now as binding parameter we need the time from php
353
    }
354
355
    /**
356
     * hook method.
357
     * not all drivers have the same boolean datatype. convert here.
358
     * 
359
     * @param boolean $boolean            
360
     * @return boolean|int|string
361
     */
362
    protected function getBooleanForDriver($boolean)
363
    {
364
        if (strstr($this->dsn, 'sqlite:') || strstr($this->dsn, 'mysql:')) {
365
            return $boolean ? 1 : 0;
366
        }
367
        // might have to be overriden for certain drivers.
368
        return $boolean;
369
    }
370
371
    /**
372
     * {@inheritDoc}
373
     */
374
    protected function updateState(Identifier $identifier, $state, $message = null)
375
    {
376
        
377
        $connection = $this->getConnection();
378
        $prefix = $this->getPrefix();
379
        try {
380
            $query = 'UPDATE ' . $prefix . 'statemachine_entities SET state = :state, 
381
                changetime = :timestamp WHERE entity_id = :entity_id 
382
                AND machine = :machine';
383
            $statement = $connection->prepare($query);
384
            $machine = $identifier->getMachine();
385
            $entity_id = $identifier->getEntityId();
386
            $timestamp =  $this->getTimestampForDriver();
387
            $statement->bindParam(":machine", $machine);
388
            $statement->bindParam(":entity_id", $entity_id);
389
            $statement->bindParam(":state", $state);
390
            $statement->bindParam(":timestamp", $timestamp);
391
            $result = $statement->execute();
392
            if ($result === false) {
393
                throw new Exception($this->getErrorInfo($statement));
394
            }
395
        } catch(\Exception $e) {
396
            throw new Exception(sprintf('query for updating state failed: [%s]', $e->getMessage()), Exception::PERSISTENCE_LAYER_EXCEPTION);
397
        }
398
    }
399
400
    /**
401
     *{@inheritDoc}
402
     */
403
    public function addHistory(Identifier $identifier, $state, $message = null, $is_exception = false)
404
    {
405
        $connection = $this->getConnection();
406
        $prefix = $this->getPrefix();
407
        try {
408
            $query = 'INSERT INTO ' . $prefix . 'statemachine_history
409
                    (machine, entity_id, state, message, changetime, exception)
410
                        VALUES
411
                    (:machine, :entity_id, :state, :message, :timestamp, :exception)';
412
            $statement = $connection->prepare($query);
413
            $machine = $identifier->getMachine();
414
            $entity_id = $identifier->getEntityId();
415
            $timestamp =  $this->getTimestampForDriver();
416
            $is_exception = $this->getBooleanForDriver($is_exception);
417
            $statement->bindParam(":machine", $machine);
418
            $statement->bindParam(":entity_id", $entity_id);
419
            $statement->bindParam(":state", $state);
420
            if($message) {
421
                if(is_string($message)) {
422
                    $info = new \stdClass();
423
                    $info->message = $message;
424
                    $message = $info;
425
                }
426
                //always json encode it so we can pass objects as the message and store it
427
                $message = json_encode($message);
428
            }
429
            $statement->bindParam(":message", $message);
430
            $statement->bindParam(":timestamp", $timestamp);
431
            $statement->bindParam(":exception", $is_exception);
432
            $result = $statement->execute();
433
            if ($result === false) {
434
                throw new Exception($this->getErrorInfo($statement));
435
            }
436
        } catch(\Exception $e) {
437
            throw new Exception(sprintf('query for updating state failed: [%s]', $e->getMessage()), Exception::PERSISTENCE_LAYER_EXCEPTION);
438
        }
439
    }
440
441
    /**
442
     * get all the ordered transition and state information for a specific
443
     * machine.
444
     * This method is public for testing purposes
445
     * 
446
     * @param string $machine            
447
     * @return [][] resultset from postgres
0 ignored issues
show
The doc-type [][] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
448
     * @throws Exception
449
     */
450
    public function getTransitions($machine)
451
    {
452
        $connection = $this->getConnection();
453
        $prefix = $this->getPrefix();
454
        $query = 'SELECT st.machine, 
455
                        st.state_from AS state_from, st.state_to AS state_to, 
456
                        st.rule, st.command,
457
                        ss_to.type AS state_to_type, 
458
        				ss_to.exit_command as state_to_exit_command,
459
        				ss_to.entry_command as state_to_entry_command, 
460
        				ss.type AS state_from_type, 
461
        				ss.exit_command as state_from_exit_command,
462
        				ss.entry_command as state_from_entry_command,
463
                        st.priority, 
464
                        ss.description AS state_from_description,
465
                        ss_to.description AS state_to_description,
466
                        st.description AS transition_description,
467
        				st.event
468
                    FROM  ' . $prefix . 'statemachine_transitions AS st
469
                    LEFT JOIN
470
                        ' . $prefix . 'statemachine_states AS ss
471
                        ON (st.state_from = ss.state AND st.machine = ss.machine)
472
                    LEFT JOIN
473
                        ' . $prefix . 'statemachine_states AS ss_to
474
                        ON (st.state_to = ss_to.state AND st.machine = ss_to.machine)
475
                    WHERE
476
                        st.machine = :machine
477
                    ORDER BY 
478
                        st.state_from ASC, st.priority ASC, st.state_to ASC';
479
        try {
480
            $statement = $connection->prepare($query);
481
            $statement->bindParam(":machine", $machine);
482
            $result = $statement->execute();
483
            
484
            if ($result === false) {
485
                throw new Exception($this->getErrorInfo($statement));
486
            }
487
            $rows = $statement->fetchAll();
488
        } catch(\Exception $e) {
489
            throw new Exception($e->getMessage(), Exception::PERSISTENCE_LAYER_EXCEPTION, $e);
490
        }
491
        
492
        return $rows;
493
    }
494
495
    /**
496
     * gets all data for transitions.
497
     * This method is public for testing purposes
498
     * 
499
     * @param string $machine
500
     *            the machine name
501
     * @return Transition[]
502
     */
503
    public function getLoaderData($machine)
504
    {
505
        $rows = $this->getTransitions($machine);
506
        
507
        $output = array();
508
        // array for local caching of states
509
        $states = array();
510
        
511
        foreach ($rows as $row) {
512
            $state_from = $row ['state_from'];
513
            $state_to = $row ['state_to'];
514
            
515
            // create the 'from' state
516
            if (isset($states [$state_from])) {
517
                $from = $states [$state_from];
518
            } else {
519
                $from = new State($row ['state_from'], $row ['state_from_type'], $row ['state_from_entry_command'], $row ['state_from_exit_command']);
520
                $from->setDescription($row ['state_from_description']);
521
            }
522
            // cache the 'from' state for the next iterations
523
            $states [$from->getName()] = $from;
524
            
525
            // create the 'to' state
526
            if (isset($states [$state_to])) {
527
                $to = $states [$state_to];
528
            } else {
529
                $to = new State($row ['state_to'], $row ['state_to_type'], $row ['state_to_entry_command'], $row ['state_to_exit_command']);
530
                $to->setDescription($row ['state_to_description']);
531
            }
532
            // cache to 'to' state for the next iterations
533
            $states [$to->getName()] = $to;
534
            
535
            // build the transition
536
            $transition = new Transition($from, $to, $row ['event'], $row ['rule'], $row ['command']);
537
            $transition->setDescription($row ['transition_description']);
538
            
539
            $output [] = $transition;
540
        }
541
        
542
        return $output;
543
    }
544
545
    /**
546
     * do some cleanup
547
     */
548
    public function __destruct()
549
    {
550
        $this->connection = null;
551
    }
552
}
553