Issues (7)

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.

src/Detector.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
3
namespace Sokil\FraudDetector;
4
5
use Sokil\DataType\PriorityList;
6
use Symfony\Component\EventDispatcher\EventDispatcher;
7
use Sokil\FraudDetector\Processor\ProcessorInterface;
8
use Sokil\FraudDetector\Collector\CollectorInterface;
9
10
class Detector
11
{
12
    const STATE_UNCHECKED   = 'unckecked';
13
    const STATE_PASSED      = 'checkPassed';
14
    const STATE_FAILED      = 'checkFailed';
15
16
    private $state = self::STATE_UNCHECKED;
17
18
    /**
19
     *
20
     * @var mixed key to identify unique user
21
     */
22
    private $key;
23
24
    /**
25
     *
26
     * @var \Sokil\DataType\PriorityList
27
     */
28
    private $processorDeclarationList;
29
30
    private $processorList = array();
31
32
    private $processorNamespaces = array(
33
        '\Sokil\FraudDetector\Processor',
34
    );
35
36
    private $collectorNamespaces = array(
37
        '\Sokil\FraudDetector\Collector',
38
    );
39
40
    private $storageNamespaces = array(
41
        '\Sokil\FraudDetector\Storage',
42
    );
43
44
    /**
45
     *
46
     * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
47
     */
48
    private $eventDispatcher;
49
50
    public function __construct()
51
    {
52
        $this->processorDeclarationList = new PriorityList();
53
        $this->eventDispatcher = new EventDispatcher();
54
    }
55
56
    /**
57
     * Key that uniquely identify user
58
     * @param type $key
59
     * @return \Sokil\FraudDetector\Detector
60
     */
61
    public function setKey($key)
62
    {
63
        $this->key = $key;
64
        return $this;
65
    }
66
67
    public function getKey()
68
    {
69
        if(!$this->key) {
70
            $this->key = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
71
        }
72
73
        return $this->key;
74
    }
75
76
    /**
77
     * Check if request is not fraud
78
     */
79
    public function check()
80
    {
81
        $this->state = self::STATE_UNCHECKED;
82
        
83
        // check all conditions
84
        /* @var $processor \Sokil\FraudDetector\ProcessorInterface */
85
        foreach($this->processorDeclarationList->getKeys() as $processorName) {
86
            $processor = $this->getProcessor($processorName);
87
88
            if($processor->isPassed()) {
89
                $processor->afterCheckPassed();
90
                $this->trigger(self::STATE_PASSED . ':' . $processorName);
91
                
92
                // check passed if all processors passes their cheks
93
                if ($this->state === self::STATE_UNCHECKED) {
94
                    $this->state = self::STATE_PASSED;
95
                }
96
            } else {
97
                $processor->afterCheckFailed();
98
                $this->trigger(self::STATE_FAILED . ':' . $processorName);
99
                // if any processor failed - all check failed
100
                $this->state = self::STATE_FAILED;
101
            }
102
        }
103
104
        $this->trigger($this->state);
105
    }
106
107
    public function registerProcessorNamespace($namespace)
108
    {
109
        $this->processorNamespaces[] = rtrim($namespace, '\\');
110
        return $this;
111
    }
112
113
    /**
114
     * Add processor identified by its name.
115
     * If processor already added, it will be replaced by new instance.
116
     *
117
     * @param string $name name of processor
118
     * @param callable $callable configurator callable
119
     * @return \Sokil\FraudDetector\Detector
120
     */
121
    public function declareProcessor($name, $callable = null, $priority = 0)
122
    {
123
        $this->processorDeclarationList->set($name, $callable, $priority);
0 ignored issues
show
$callable is of type callable|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
124
        return $this;
125
    }
126
127
    public function addProcssor($name, ProcessorInterface $processor, $priority = 0)
128
    {
129
        $this->declareProcessor($name, null, $priority);
130
        $this->processorList[$name] = $processor;
131
132
        return $this;
133
    }
134
135
    public function isProcessorDeclared($name)
136
    {
137
        return $this->processorDeclarationList->has($name);
138
    }
139
140
    /**
141
     * Factory method to create new check condition
142
     *
143
     * @param string $name name of check condition
144
     * @return \Sokil\FraudDetector\ProcessorInterface
145
     * @throws \Exception
146
     */
147
    private function getProcessorClassName($name)
148
    {
149
        $className = ucfirst($name) . 'Processor';
150
151
        foreach($this->processorNamespaces as $namespace) {
152
            $fullyQualifiedClassName = $namespace . '\\' . $className;
153
            if(class_exists($fullyQualifiedClassName)) {
154
                return $fullyQualifiedClassName;
155
            }
156
        }
157
158
        throw new \Exception('Class ' . $fullyQualifiedClassName . ' not found');
159
    }
160
161
    public function getProcessor($processorName)
162
    {
163
        if(isset($this->processorList[$processorName])) {
164
            return $this->processorList[$processorName];
165
        }
166
167
        // create processor
168
        $processorClassName = $this->getProcessorClassName($processorName);
169
        $processor =  new $processorClassName($this);
170
171
        if (!($processor instanceof ProcessorInterface)) {
172
            throw new \Exception('Processor must inherit ProcessorInterface');
173
        }
174
175
        // configure processor
176
        $configuratorCallable = $this->processorDeclarationList->get($processorName);
177
        if($configuratorCallable && is_callable($configuratorCallable)) {
178
            call_user_func($configuratorCallable, $processor, $this);
179
        }
180
181
        $this->processorList[$processorName] = $processor;
182
183
        return $processor;
184
    }
185
186
    public function registerCollectorNamespace($namespace)
187
    {
188
        $this->collectorNamespaces[] = rtrim($namespace, '\\');
189
        return $this;
190
    }
191
192 View Code Duplication
    private function getCollectorClassName($type)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
    {
194
        if(false == strpos($type, '_')) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($type, '_') of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
195
            $className = ucfirst($type);
196
        } else {
197
            $className = implode('', array_map('ucfirst', explode('_', $type)));
198
        }
199
200
        $className .= 'Collector';
201
202
        foreach($this->collectorNamespaces as $namespace) {
203
            $fullyQualifiedClassName = $namespace . '\\' . $className;
204
            if(class_exists($fullyQualifiedClassName)) {
205
                return $fullyQualifiedClassName;
206
            }
207
        }
208
209
        throw new \Exception('Class ' . $fullyQualifiedClassName . ' not found');
210
    }
211
212
    /*
213
     * @param int $requestNumber maximum number of allowed requests
214
     * @param int $timeInterval time interval in seconds
215
     */
216
    public function createCollector(
217
        $type,
218
        $namespace,
219
        $requestNumber,
220
        $timeInterval,
221
        $configuratorCallable = null
222
    ) {
223
        $className = $this->getCollectorClassName($type);
224
225
        $collector = new $className(
226
            $this->getKey() . ':' . $namespace,
227
            $requestNumber,
228
            $timeInterval
229
        );
230
231
        if (!($collector instanceof CollectorInterface)) {
232
            throw new \Exception('Collector must inherit CollectorInterface');
233
        }
234
235
        // configure
236
        if(is_callable($configuratorCallable)) {
237
            call_user_func($configuratorCallable, $collector, $this);
238
        }
239
240
        return $collector;
241
242
    }
243
244 View Code Duplication
    private function getStorageClassName($type)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
    {
246
        if(false == strpos($type, '_')) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($type, '_') of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
247
            $className = ucfirst($type);
248
        } else {
249
            $className = implode('', array_map('ucfirst', explode('_', $type)));
250
        }
251
252
        $className .= 'Storage';
253
254
        foreach($this->storageNamespaces as $namespace) {
255
            $fullyQualifiedClassName = $namespace . '\\' . $className;
256
            if(class_exists($fullyQualifiedClassName)) {
257
                return $fullyQualifiedClassName;
258
            }
259
        }
260
261
        throw new \Exception('Class ' . $fullyQualifiedClassName . ' not found');
262
    }
263
264
    public function createStorage($type, $configuratorCallable = null)
265
    {
266
        $className = $this->getStorageClassName($type);
267
268
        $storage = new $className();
269
270
        // configure
271
        if(is_callable($configuratorCallable)) {
272
            call_user_func($configuratorCallable, $storage);
273
        }
274
275
        return $storage;
276
277
    }
278
279
    public function registerStorageNamespace($namespace)
280
    {
281
        $this->storageNamespaces[] = rtrim($namespace, '\\');
282
        return $this;
283
    }
284
285
    private function on($stateName, $callable)
286
    {
287
        if($this->hasState(self::STATE_UNCHECKED)) {
288
            $this->subscribe($stateName, $callable);
289
        } elseif($this->hasState($stateName)) {
290
            call_user_func($callable);
291
        }
292
293
        return $this;
294
    }
295
296
    public function onCheckPassed($callable)
297
    {
298
        $this->on(self::STATE_PASSED, $callable);
299
300
        return $this;
301
    }
302
303
    public function onCheckFailed($callable)
304
    {
305
        $this->on(self::STATE_FAILED, $callable);
306
307
        return $this;
308
    }
309
310
    public function isUnchecked()
311
    {
312
        return $this->hasState(self::STATE_UNCHECKED);
313
    }
314
315
    public function isPassed()
316
    {
317
        return $this->hasState(self::STATE_PASSED);
318
    }
319
320
    public function isFailed()
321
    {
322
        return $this->hasState(self::STATE_FAILED);
323
    }
324
325
    private function hasState($state)
326
    {
327
        return $this->state === $state;
328
    }
329
330
    public function subscribe($eventName, $callable, $priority = 0)
331
    {
332
        $this->eventDispatcher->addListener($eventName, $callable, $priority);
333
        return $this;
334
    }
335
336
    /**
337
     *
338
     * @param string $eventName
339
     * @param mixed $target
340
     * @return \Sokil\FraudDetector\Event
341
     */
342
    public function trigger($eventName, $target = null)
343
    {
344
        $event = new Event();
345
346
        if($target) {
347
            $event->setTarget($target);
348
        }
349
350
        return $this->eventDispatcher->dispatch($eventName, $event);
351
    }
352
}
353