|
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; |
|
|
|
|
|
|
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() |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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 |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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')) { |
|
|
|
|
|
|
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 |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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 |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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
|
|
|
[ |
|
|
|
|
|
|
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 |
|
|
|
|
|
|
324
|
|
|
*/ |
|
325
|
|
View Code Duplication |
private function getSecurityUser() |
|
|
|
|
|
|
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 |
|
|
|
|
|
|
339
|
|
|
*/ |
|
340
|
|
|
public function getSecurityUsername() |
|
341
|
|
|
{ |
|
342
|
|
|
// No securityUser, no tracking |
|
343
|
|
|
if (!($this->securityUser = $this->getSecurityUser())) { |
|
|
|
|
|
|
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)) { |
|
|
|
|
|
|
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
|
|
|
|
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
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. 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.