Completed
Push — feature/EVO-7278-tracking-info... ( d73a1e...c12eb7 )
by
unknown
66:18
created

ActivityManager::getHeaderLink()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
rs 8.8571
cc 5
eloc 11
nc 7
nop 2
1
<?php
2
/**
3
 * Keeping all activity in one place to be controlled
4
 */
5
6
namespace Graviton\AuditTrackingBundle\Manager;
7
8
use Graviton\AuditTrackingBundle\Document\AuditTracking;
9
use Graviton\RestBundle\Event\ModelEvent;
10
use Graviton\SecurityBundle\Entities\SecurityUser;
11
use Guzzle\Http\Message\Header;
12
use Symfony\Bridge\Doctrine\ManagerRegistry;
13
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
14
use Symfony\Component\HttpFoundation\RequestStack;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
17
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
18
19
use Symfony\Component\HttpFoundation\Request;
20
use Symfony\Component\Security\Core\User\UserInterface;
21
22
/**
23
 * Class ActivityManager
24
 * @package Graviton\AuditTrackingBundle\Manager
25
 *
26
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
27
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
28
 * @link     http://swisscom.ch
29
 */
30
class ActivityManager
31
{
32
    /** Max char length of saved content data */
33
    const CONTENT_MAX_LENGTH = 2048;
34
35
    /** @var bool If log is enabled */
36
    private $enabled = false;
37
38
    /** @var Request $request */
39
    private $request;
40
41
    /** @var array */
42
    private $configurations;
43
44
    /** @var TokenStorage */
45
    protected $tokenStorage;
46
47
    /** @var SecurityUser */
48
    private $securityUser;
49
50
    /** @var AuditTracking */
51
    private $document;
52
53
    /** @var array Events that shall be stored */
54
    private $events = [];
55
56
    /**
57
     * DBActivityListener constructor.
58
     *
59
     * @param RequestStack  $requestStack Sf request data
60
     * @param TokenStorage  $tokenStorage Sf Auth token storage
61
     * @param AuditTracking $document     DocumentCollection for event
62
     */
63
    public function __construct(
64
        RequestStack  $requestStack,
65
        TokenStorage  $tokenStorage,
66
        AuditTracking $document
67
    ) {
68
        $this->request = $requestStack ? $requestStack->getCurrentRequest() : false;
0 ignored issues
show
Documentation Bug introduced by
It seems like $requestStack ? $request...urrentRequest() : false can also be of type false. However, the property $request is declared as type object<Symfony\Component\HttpFoundation\Request>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
69
        $this->tokenStorage = $tokenStorage;
70
        $this->document = $document;
71
    }
72
73
    /**
74
     * Set permission and access configuration
75
     *
76
     * @param array $configurations key value config
77
     * @return void
78
     */
79
    public function setConfiguration(array $configurations)
80
    {
81
        $this->configurations = $configurations;
82
        if ($this->runTracking()) {
83
            $this->enabled = true;
84
        }
85
    }
86
87
    /**
88
     * Return casted value from configuration.
89
     *
90
     * @param string $key  Configuration key
91
     * @param string $cast Type of object is expected to be returned
92
     * @return int|string|bool|array
93
     * @throws ParameterNotFoundException
94
     */
95
    private function getConfigValue($key, $cast = 'string')
96
    {
97
        if (array_key_exists($key, $this->configurations)) {
98
            if ('bool' == $cast) {
99
                return (boolean) $this->configurations[$key];
100
            }if ('array' == $cast) {
101
                return (array) $this->configurations[$key];
102
            } elseif ('string' == $cast) {
103
                return (string) $this->configurations[$key];
104
            } elseif ('int' == $cast) {
105
                return (int) $this->configurations[$key];
106
            }
107
        }
108
        throw new ParameterNotFoundException('ActivityManager could not find required configuration: '.$key);
109
    }
110
111
    /**
112
     * Check if this the Call has to be logged
113
     *
114
     * @return bool
115
     */
116
    private function runTracking()
0 ignored issues
show
Coding Style introduced by
function runTracking() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
117
    {
118
        //Ignore if no request, import fixtures.
119
        if (!$this->request) {
120
            return false;
121
        }
122
123
        // Check if enable
124
        if (!$this->getConfigValue('log_enabled', 'bool')) {
125
            return false;
126
        }
127
        
128
        // We never log tracking service calls
129
        if ((strpos($this->request->getRequestUri(), '/_debug') !== false)) {
130
            return false;
131
        }
132
133
        // Check if we wanna log test and localhost calls
134
        if (!$this->getConfigValue('log_test_calls', 'bool')
135
            && !in_array($this->request->getHost(), ['localhost', '127.0.0.1'])) {
136
            return false;
137
        }
138
139
        return true;
140
    }
141
142
    /**
143
     * Incoming request done by user
144
     * @param Request $request sf response priority 1
145
     * @return void
146
     */
147 View Code Duplication
    public function registerRequestEvent(Request $request)
0 ignored issues
show
Duplication introduced by
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...
148
    {
149
        if (!$this->enabled) {
150
            return;
151
        }
152
        // Check if this request event shall be registered
153
        $saveEvents = $this->getConfigValue('requests', 'array');
154
        $method = $request->getMethod();
155
        if (!in_array($method, $saveEvents)) {
156
            return;
157
        }
158
159
        $content = substr($request->getContent(), 0, self::CONTENT_MAX_LENGTH);
160
161
        $data = ['ip' => $request->getClientIp()];
162
163
        if ($this->getConfigValue('request_headers', 'bool')) {
164
            $data['headers'] = $request->headers->all();
165
        }
166
        if ($length=$this->getConfigValue('request_content', 'int')) {
167
            $cnt = mb_check_encoding($content, 'UTF-8') ? $content : 'Content omitted, since it is not utf-8';
168
            $data['content'] = ($length==1) ? $cnt : substr($cnt, 0, $length);
169
        }
170
171
        $this->createEvent(
172
            'request',
173
            $request->getRequestUri(),
174
            $method,
175
            $data
0 ignored issues
show
Documentation introduced by
$data is of type array<string,string|array>, 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...
176
        );
177
    }
178
179
    /**
180
     * The response returned to user
181
     *
182
     * @param Response $response sf response
183
     * @return void
184
     */
185 View Code Duplication
    public function registerResponseEvent(Response $response)
0 ignored issues
show
Duplication introduced by
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...
186
    {
187
        if (!$this->enabled) {
188
            return;
189
        }
190
        if (!$this->getConfigValue('response', 'bool')) {
191
            return;
192
        }
193
194
        $data = [];
195
        $statusCode = '0';
196
197
        if (method_exists($response, 'getStatusCode')) {
198
            $statusCode = $response->getStatusCode();
199
        }
200
        if ($length=$this->getConfigValue('response_content', 'int') && method_exists($response, 'getContent')) {
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $length = ($this->getCon...esponse, 'getContent')), Probably Intended Meaning: ($length = $this->getCon...response, 'getContent')
Loading history...
201
            $cnt = mb_check_encoding($response->getContent(), 'UTF-8') ?
202
                $response->getContent() : 'Content omitted, since it is not utf-8';
203
            $data['content'] = ($length==1) ? $cnt : substr($cnt, 0, $length);
204
            $data['header']  = $response->headers->all();
205
        }
206
        // Header links
207
        $location = $this->getHeaderLink($response->headers->get('link'), 'self');
208
209
        $this->createEvent(
210
            'response',
211
            $statusCode,
212
            $location,
213
            $data
0 ignored issues
show
Documentation introduced by
$data is of type array, 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...
214
        );
215
    }
216
217
    /**
218
     * Capture possible un-handled exceptions in php
219
     *
220
     * @param \Exception $exception The exception thrown in service.
221
     * @return void
222
     */
223 View Code Duplication
    public function registerExceptionEvent(\Exception $exception)
0 ignored issues
show
Duplication introduced by
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...
224
    {
225
        if (!$this->enabled) {
226
            return;
227
        }
228
        if (!$this->getConfigValue('exceptions', 'bool')) {
229
            return;
230
        }
231
        $data = [
232
            'message'   => $exception->getMessage(),
233
            'trace'     => $exception->getTraceAsString()
234
        ];
235
        $this->createEvent(
236
            'exception',
237
            $exception->getCode(),
238
            get_class($exception),
239
            $data
0 ignored issues
show
Documentation introduced by
$data is of type array<string,string,{"me...ing","trace":"string"}>, 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...
240
        );
241
    }
242
243
    /**
244
     * Any database events, update, save or delete
245
     *
246
     * Available $event->getCollection() would give you the full object.
247
     *
248
     * @param ModelEvent $event Document object changed
249
     * @return void
250
     */
251 View Code Duplication
    public function registerDocumentModelEvent(ModelEvent $event)
0 ignored issues
show
Duplication introduced by
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...
252
    {
253
        if (!$this->enabled) {
254
            return;
255
        }
256
        if ((!($dbEvents = $this->getConfigValue('database', 'array')))) {
257
            return;
258
        }
259
        if (!in_array($event->getAction(), $dbEvents)) {
260
            return;
261
        }
262
263
        $this->createEvent(
264
            $event->getAction(),
265
            'collection',
266
            'DocumentModel',
267
            [
0 ignored issues
show
Documentation introduced by
array('class' => $event->getCollectionClass()) is of type array<string,string,{"class":"string"}>, 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...
268
                'class' => $event->getCollectionClass()
269
            ],
270
            $event->getCollectionId(),
271
            $event->getCollectionName()
272
        );
273
    }
274
275
    /**
276
     * Creating the Document to be saved into DB.
277
     *
278
     * @param string $action         What did happen
279
     * @param string $type           What was it
280
     * @param string $location       Where was it
281
     * @param string $data           Aditioanl data
282
     * @param string $collectionId   Modified collection identifier
283
     * @param string $collectionName Modified collection name
284
     *
285
     * @return void
286
     */
287
    private function createEvent(
288
        $action,
289
        $type,
290
        $location,
291
        $data,
292
        $collectionId = '',
293
        $collectionName = ''
294
    ) {
295
        if (!is_object($data)) {
296
            $data = (object) $data;
297
        }
298
299
        /** @var AuditTracking $event */
300
        $event = new $this->document();
301
302
        $event->setAction($action);
303
        $event->setType($type);
304
        $event->setData($data);
305
        $event->setLocation($location);
306
307
        if ($collectionId) {
308
            $event->setCollectionId($collectionId);
309
        }
310
        if ($collectionName) {
311
            $event->setCollectionName($collectionName);
312
        }
313
314
        $event->setCreatedAt(new \DateTime());
315
316
        $this->events[] = $event;
317
    }
318
319
    
320
    /**
321
     * Find current user
322
     *
323
     * @return string|bool
0 ignored issues
show
Documentation introduced by
Should the return type not be UserInterface|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
324
     */
325 View Code Duplication
    private function getSecurityUser()
0 ignored issues
show
Duplication introduced by
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...
326
    {
327
        /** @var PreAuthenticatedToken $token */
328
        if (($token = $this->tokenStorage->getToken())
329
            && ($user = $token->getUser()) instanceof UserInterface ) {
330
            return $user;
331
        }
332
        return false;
333
    }
334
335
    /**
336
     * Last check before saving the Event into DB
337
     *
338
     * @return bool|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
339
     */
340
    public function getSecurityUsername()
341
    {
342
        // No securityUser, no tracking
343
        if (!($this->securityUser = $this->getSecurityUser())) {
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getSecurityUser() of type object<Symfony\Component...ore\User\UserInterface> or false is incompatible with the declared type object<Graviton\Security...\Entities\SecurityUser> of property $securityUser.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
344
            return false;
345
        }
346
347
        // Check if we wanna log test and localhost calls
348
        if (!$this->getConfigValue('log_test_calls', 'bool')) {
349
            if (!$this->securityUser->hasRole(SecurityUser::ROLE_CONSULTANT)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Security\Core\User\UserInterface as the method hasRole() does only exist in the following implementations of said interface: Graviton\SecurityBundle\Entities\SecurityUser.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
350
                return false;
351
            }
352
        }
353
354
        return $this->securityUser->getUsername();
355
    }
356
357
    /**
358
     * Parse and extract customer header links
359
     *
360
     * @param string $links  sf header links
361
     * @param string $return desired key to be found
362
     * @return string
363
     */
364
    private function getHeaderLink($links, $return = 'self')
365
    {
366
        if (!$links) {
367
            return '';
368
        }
369
370
        $parts = [];
371
        $link = '';
372
        foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $links) as $key => $part) {
373
            if ($key%2) {
374
                $parts[str_replace(['rel=','"'], '', trim($part))] = $link;
375
            } else {
376
                $link = str_replace(['<','>'], '', $part);
377
            }
378
        }
379
380
        return  array_key_exists($return, $parts) ? $parts[$return] : '';
381
    }
382
383
    /**
384
     * @return array
385
     */
386
    public function getEvents()
387
    {
388
        return $this->events;
389
    }
390
}
391