Completed
Push — feature/EVO-7278-tracking-info... ( 70565f...ea41d2 )
by
unknown
08:58
created

ActivityManager::extractHeaderLink()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.0187

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 10
cts 11
cp 0.9091
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 9
nc 7
nop 2
crap 5.0187
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 Guzzle\Http\Message\Header;
11
use Symfony\Bridge\Doctrine\ManagerRegistry;
12
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
13
use Symfony\Component\HttpFoundation\RequestStack;
14
use Symfony\Component\HttpFoundation\Response;
15
16
use Symfony\Component\HttpFoundation\Request;
17
18
/**
19
 * Class ActivityManager
20
 * @package Graviton\AuditTrackingBundle\Manager
21
 *
22
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
23
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
24
 * @link     http://swisscom.ch
25
 */
26
class ActivityManager
27
{
28
    /** Max char length of saved content data */
29
    const CONTENT_MAX_LENGTH = 2048;
30
31
    /** @var bool If log is enabled */
32
    private $enabled = false;
33
34
    /** @var Request $request */
35
    private $request;
36
37
    /** @var array */
38
    private $configurations;
39
40
    /** @var AuditTracking */
41
    private $document;
42
43
    /** @var array Events that shall be stored */
44
    private $events = [];
45
46
    /** @var string  */
47
    private $globalRequestLocation = '';
48
49
    /**
50
     * DBActivityListener constructor.
51
     *
52
     * @param RequestStack  $requestStack Sf request data
53
     * @param AuditTracking $document     DocumentCollection for event
54
     */
55 4
    public function __construct(
56
        RequestStack  $requestStack,
57
        AuditTracking $document
58
    ) {
59 4
        $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...
60 4
        $this->document = $document;
61 4
    }
62
63
    /**
64
     * Set permission and access configuration
65
     *
66
     * @param array $configurations key value config
67
     * @return void
68
     */
69 4
    public function setConfiguration(array $configurations)
70
    {
71 4
        $this->configurations = $configurations;
72 4
        if ($this->runTracking()) {
73
            $this->enabled = true;
74
        }
75 4
    }
76
77
    /**
78
     * Return casted value from configuration.
79
     *
80
     * @param string $key  Configuration key
81
     * @param string $cast Type of object is expected to be returned
82
     * @return int|string|bool|array
83
     * @throws ParameterNotFoundException
84
     */
85 2
    public function getConfigValue($key, $cast = 'string')
86
    {
87 2
        if (array_key_exists($key, $this->configurations)) {
88 2
            if ('bool' == $cast) {
89 2
                return (boolean) $this->configurations[$key];
90 2
            }if ('array' == $cast) {
91 2
                return (array) $this->configurations[$key];
92 2
            } elseif ('string' == $cast) {
93 2
                return (string) $this->configurations[$key];
94 2
            } elseif ('int' == $cast) {
95 2
                return (int) $this->configurations[$key];
96
            }
97
        }
98
        throw new ParameterNotFoundException('ActivityManager could not find required configuration: '.$key);
99
    }
100
101
    /**
102
     * Check if this the Call has to be logged
103
     *
104
     * @return bool
105
     */
106 4
    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...
107
    {
108
        //Ignore if no request, import fixtures.
109 4
        if (!$this->request) {
110 4
            return false;
111
        }
112
113
        // Check if enable
114
        if (!$this->getConfigValue('log_enabled', 'bool')) {
115
            return false;
116
        }
117
        
118
        // We never log tracking service calls
119
        $excludeUrls = $this->getConfigValue('exlude_urls', 'array');
120
        if ($excludeUrls) {
121
            $currentUrl = $this->request->getRequestUri();
122
            foreach ($excludeUrls as $url) {
0 ignored issues
show
Bug introduced by
The expression $excludeUrls of type boolean|array|string|integer is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

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