|
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; |
|
|
|
|
|
|
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() |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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')) { |
|
|
|
|
|
|
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
|
|
|
|
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.