Issues (3882)

Security Analysis    39 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  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.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Response Splitting (9)
Response Splitting can be used to send arbitrary responses.
  File Manipulation (2)
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.
  File Exposure (7)
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.
  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.
  Code Injection (13)
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  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.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  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.
  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.
  Cross-Site Scripting (8)
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.
  Header Injection
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.

app/EventHandler.php (1 issue)

1
<?php
2
3
namespace App;
4
5
/**
6
 * Event Handler main class.
7
 *
8
 * @package App
9
 *
10
 * @copyright YetiForce S.A.
11
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
12
 * @author    Mariusz Krzaczkowski <[email protected]>
13
 * @author    RadosÅ‚aw Skrzypczak <[email protected]>
14
 */
15
class EventHandler
16
{
17
	/**
18
	 * Table name.
19
	 *
20
	 * @var string
21
	 */
22
	protected static $baseTable = 'vtiger_eventhandlers';
23
	private static $mandatoryEventClass = ['ModTracker_ModTrackerHandler_Handler'];
24
	private $recordModel;
25
	private $moduleName;
26
	private $params;
27
	private $exceptions = [];
28
	private $handlers = [];
29
30
	/** @var int Handler is in system mode, no editing possible */
31
	public const SYSTEM = 0;
32
	/** @var int Handler is in edit mode */
33
	public const EDITABLE = 1;
34
35 2
	/** @var string Edit view, validation before saving */
36
	public const EDIT_VIEW_PRE_SAVE = 'EditViewPreSave';
37 2
	/** @var string Edit view, change value */
38
	public const EDIT_VIEW_CHANGE_VALUE = 'EditViewChangeValue';
39
	/** @var string Record converter after create record */
40 2
	public const RECORD_CONVERTER_AFTER_SAVE = 'RecordConverterAfterSave';
41 2
42
	/**
43 2
	 * Handler types.
44 2
	 *
45 2
	 * @var array
46 2
	 */
47
	public const HANDLER_TYPES = [
48
		'EditViewPreSave' => [
49
			'label' => 'LBL_EDIT_VIEW_PRESAVE',
50 2
			'icon' => 'fas fa-step-backward',
51
			'columns' => [
52
				'eventName' => ['label' => 'LBL_EVENT_NAME'],
53
				'eventDescription' => ['label' => 'LBL_EVENT_DESC'],
54
				'modules' => ['label' => 'LBL_INCLUDE_MODULES'],
55
				'modulesExcluded' => ['label' => 'LBL_EXCLUDE_MODULES'],
56
				'active' => ['label' => 'LBL_EVENT_IS_ACTIVE'],
57
			],
58
		],
59
		'EntityChangeState' => [
60
			'label' => 'LBL_ENTITY_CHANGE_STATE',
61 5799
			'icon' => 'fas fa-compass',
62
			'columns' => [
63 5799
				'eventName' => ['label' => 'LBL_EVENT_NAME'],
64 2
				'eventDescription' => ['label' => 'LBL_EVENT_DESC'],
65 2
				'modules' => ['label' => 'LBL_INCLUDE_MODULES'],
66 2
				'modulesExcluded' => ['label' => 'LBL_EXCLUDE_MODULES'],
67
				'active' => ['label' => 'LBL_EVENT_IS_ACTIVE'],
68 2
			],
69
		],
70 5799
		'EntityBeforeSave' => [
71 5799
			'label' => 'LBL_ENTITY_BEFORE_SAVE',
72 5796
			'icon' => 'fas fa-save',
73 5796
			'columns' => [
74 28
				'eventName' => ['label' => 'LBL_EVENT_NAME'],
75
				'eventDescription' => ['label' => 'LBL_EVENT_DESC'],
76
				'modules' => ['label' => 'LBL_INCLUDE_MODULES'],
77
				'modulesExcluded' => ['label' => 'LBL_EXCLUDE_MODULES'],
78 5799
				'active' => ['label' => 'LBL_EVENT_IS_ACTIVE'],
79
			],
80
		],
81
		'EntityAfterSave' => [
82
			'label' => 'LBL_ENTITY_AFTER_SAVE',
83
			'icon' => 'far fa-save',
84
			'columns' => [
85
				'eventName' => ['label' => 'LBL_EVENT_NAME'],
86
				'eventDescription' => ['label' => 'LBL_EVENT_DESC'],
87
				'modules' => ['label' => 'LBL_INCLUDE_MODULES'],
88
				'modulesExcluded' => ['label' => 'LBL_EXCLUDE_MODULES'],
89
				'active' => ['label' => 'LBL_EVENT_IS_ACTIVE'],
90
			],
91
		],
92
		'DetailViewBefore' => [
93
			'label' => 'LBL_DETAIL_VIEW_BEFORE',
94
			'icon' => 'mdi mdi-account-details c-mdi',
95
			'columns' => [
96
				'eventName' => ['label' => 'LBL_EVENT_NAME'],
97
				'eventDescription' => ['label' => 'LBL_EVENT_DESC'],
98
				'modules' => ['label' => 'LBL_INCLUDE_MODULES'],
99
				'modulesExcluded' => ['label' => 'LBL_EXCLUDE_MODULES'],
100
				'active' => ['label' => 'LBL_EVENT_IS_ACTIVE'],
101
			],
102
		],
103
		'EditViewBefore' => [
104
			'label' => 'LBL_EDIT_VIEW_BEFORE',
105
			'icon' => 'yfi yfi-full-editing-view ',
106
			'columns' => [
107
				'eventName' => ['label' => 'LBL_EVENT_NAME'],
108
				'eventDescription' => ['label' => 'LBL_EVENT_DESC'],
109
				'modules' => ['label' => 'LBL_INCLUDE_MODULES'],
110
				'modulesExcluded' => ['label' => 'LBL_EXCLUDE_MODULES'],
111
				'active' => ['label' => 'LBL_EVENT_IS_ACTIVE'],
112
			],
113 2
		],
114
		'EditViewDuplicate' => [
115 2
			'label' => 'LBL_EDIT_VIEW_DUPLICATE',
116 2
			'icon' => 'fas fa-clone',
117 2
			'columns' => [
118
				'eventName' => ['label' => 'LBL_EVENT_NAME'],
119
				'eventDescription' => ['label' => 'LBL_EVENT_DESC'],
120
				'modules' => ['label' => 'LBL_INCLUDE_MODULES'],
121
				'modulesExcluded' => ['label' => 'LBL_EXCLUDE_MODULES'],
122
				'active' => ['label' => 'LBL_EVENT_IS_ACTIVE'],
123
			],
124
		],
125
		'InventoryRecordDetails' => [
126
			'label' => 'LBL_INVENTORY_RECORD_DETAILS',
127
			'icon' => 'fas fa-pallet',
128
			'columns' => [
129
				'eventName' => ['label' => 'LBL_EVENT_NAME'],
130
				'eventDescription' => ['label' => 'LBL_EVENT_DESC'],
131
				'modules' => ['label' => 'LBL_INCLUDE_MODULES'],
132
				'modulesExcluded' => ['label' => 'LBL_EXCLUDE_MODULES'],
133
				'active' => ['label' => 'LBL_EVENT_IS_ACTIVE'],
134
			],
135
		],
136
	];
137
138
	/**
139
	 * Get all event handlers.
140
	 *
141
	 * @param bool $active
142
	 *
143
	 * @return array
144
	 */
145
	public static function getAll(bool $active = true): array
146
	{
147
		$query = (new \App\Db\Query())->from(self::$baseTable)->orderBy(['priority' => SORT_DESC]);
148
		if ($active) {
149
			$query->where(['is_active' => 1]);
150
		}
151
		return $query->indexBy('eventhandler_id')->all();
152
	}
153
154
	/**
155 1
	 * Get active event handlers by type (event_name).
156
	 *
157 1
	 * @param string $name
158 1
	 * @param string $moduleName
159
	 * @param bool   $active
160
	 *
161 1
	 * @return array
162 1
	 */
163 1
	public static function getByType(string $name, ?string $moduleName = '', bool $active = true): array
164 1
	{
165
		$handlersByType = [];
166
		$cacheName = 'All' . ($active ? ':active' : '');
167
		if (Cache::has('EventHandlerByType', $cacheName)) {
168
			$handlersByType = Cache::get('EventHandlerByType', $cacheName);
169
		} else {
170
			foreach (self::getAll($active) as $handler) {
171
				$handlersByType[$handler['event_name']][$handler['handler_class']] = $handler;
172 1
			}
173
			Cache::save('EventHandlerByType', $cacheName, $handlersByType, Cache::LONG);
174 1
		}
175 1
		$handlers = $handlersByType[$name] ?? [];
176
		if ($moduleName) {
177
			foreach ($handlers as $key => $handler) {
178 1
				if ((!empty($handler['include_modules']) && !\in_array($moduleName, explode(',', $handler['include_modules']))) || (!empty($handler['exclude_modules']) && \in_array($moduleName, explode(',', $handler['exclude_modules'])))) {
179 1
					unset($handlers[$key]);
180 1
				}
181 1
			}
182
		}
183
		return $handlers;
184
	}
185
186
	/**
187
	 * Get vars event handlers by type (event_name).
188 5796
	 *
189
	 * @param string $name
190 5796
	 * @param string $moduleName
191 5796
	 * @param array  $params
192
	 * @param bool   $byKey
193
	 *
194
	 * @return string
195
	 */
196
	public static function getVarsByType(string $name, string $moduleName, array $params, bool $byKey = false): string
197
	{
198 5796
		$return = [];
199
		foreach (self::getByType($name, $moduleName) as $key => $handler) {
200 5796
			$className = $handler['handler_class'];
201 5796
			if (method_exists($className, 'vars') && ($vars = (new $className())->vars($name, $params, $moduleName))) {
202
				if ($byKey) {
203
					$return[$key] = $vars;
204
				} else {
205
					$return = array_values(array_unique(array_merge($return, $vars)));
206
				}
207
			}
208 4
		}
209
		return Purifier::encodeHtml(Json::encode($return));
210 4
	}
211 4
212
	/**
213
	 * Register an event handler.
214
	 *
215
	 * @param string $eventName      The name of the event to handle
216
	 * @param string $className
217
	 * @param string $includeModules
218
	 * @param string $excludeModules
219
	 * @param int    $priority
220
	 * @param bool   $isActive
221
	 * @param int    $ownerId
222
	 * @param int    $mode
223
	 *
224
	 * @return bool
225
	 */
226
	public static function registerHandler(string $eventName, string $className, $includeModules = '', $excludeModules = '', $priority = 5, $isActive = true, $ownerId = 0, $mode = 1): bool
227
	{
228
		$return = false;
229
		$isExists = (new \App\Db\Query())->from(self::$baseTable)->where(['event_name' => $eventName, 'handler_class' => $className])->exists();
230 5796
		if (!$isExists) {
231
			$return = \App\Db::getInstance()->createCommand()
232 5796
				->insert(self::$baseTable, [
233
					'event_name' => $eventName,
234
					'handler_class' => $className,
235
					'is_active' => $isActive,
236
					'include_modules' => $includeModules,
237
					'exclude_modules' => $excludeModules,
238
					'priority' => $priority,
239
					'owner_id' => $ownerId,
240 5796
					'privileges' => $mode,
241
				])->execute();
242 5796
			static::clearCache();
243
		}
244
		return $return;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $return could return the type integer which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
245
	}
246
247
	/**
248
	 * Clear cache.
249
	 *
250 1
	 * @return void
251
	 */
252 1
	public static function clearCache(): void
253
	{
254
		Cache::delete('EventHandlerByType', 'All');
255
		Cache::delete('EventHandlerByType', 'All:active');
256
	}
257
258
	/**
259
	 * Unregister a registered handler.
260 1
	 *
261
	 * @param string      $className
262 1
	 * @param bool|string $eventName
263 1
	 */
264
	public static function deleteHandler($className, $eventName = false)
265
	{
266
		$params = ['handler_class' => $className];
267
		if ($eventName) {
268
			$params['event_name'] = $eventName;
269
		}
270 5799
		\App\Db::getInstance()->createCommand()->delete(self::$baseTable, $params)->execute();
271
		static::clearCache();
272 5799
	}
273 5799
274 1
	/**
275 1
	 * Update an event handler.
276 1
	 *
277 1
	 * @param array $params
278 1
	 * @param int   $id
279
	 *
280
	 * @return void
281 1
	 */
282 1
	public static function update(array $params, int $id)
283
	{
284 1
		Db::getInstance()->createCommand()->update(self::$baseTable, $params, ['eventhandler_id' => $id])->execute();
285
		static::clearCache();
286
	}
287 1
288
	/**
289
	 * Check if it is active function.
290
	 *
291
	 * @param string      $className
292
	 * @param string|null $eventName
293 5799
	 *
294
	 * @return bool
295
	 */
296
	public static function checkActive(string $className, ?string $eventName = null): bool
297
	{
298
		$rows = (new \App\Db\Query())->from(self::$baseTable)->where(['handler_class' => $className])->all();
299
		$status = false;
300
		foreach ($rows as $row) {
301
			if (isset($eventName) && $eventName !== $row['event_name']) {
302
				continue;
303 5799
			}
304
			if (empty($row['is_active'])) {
305 5799
				return false;
306 5796
			}
307 5795
			$status = true;
308
		}
309 3
		return $status;
310 3
	}
311
312 5796
	/**
313 5796
	 * Set an event handler as inactive.
314 5796
	 *
315
	 * @param string      $className
316
	 * @param bool|string $eventName
317
	 */
318
	public static function setInActive($className, $eventName = false)
319
	{
320 5799
		$params = ['handler_class' => $className];
321
		if ($eventName) {
322
			$params['event_name'] = $eventName;
323
		}
324
		\App\Db::getInstance()->createCommand()
325
			->update(self::$baseTable, ['is_active' => false], $params)->execute();
326
		static::clearCache();
327
	}
328
329
	/**
330
	 * Set an event handler as active.
331
	 *
332
	 * @param string      $className
333
	 * @param bool|string $eventName
334
	 */
335
	public static function setActive($className, $eventName = false)
336
	{
337
		$params = ['handler_class' => $className];
338
		if ($eventName) {
339
			$params['event_name'] = $eventName;
340
		}
341
		\App\Db::getInstance()->createCommand()->update(self::$baseTable, ['is_active' => true], $params)->execute();
342
		static::clearCache();
343
	}
344
345
	/**
346
	 * Set record model.
347
	 *
348
	 * @param \Vtiger_Record_Model $recordModel
349
	 *
350
	 * @return $this
351
	 */
352
	public function setRecordModel(\Vtiger_Record_Model $recordModel)
353
	{
354
		$this->recordModel = $recordModel;
355
		$this->moduleName = $recordModel->getModuleName();
356
		return $this;
357
	}
358
359
	/**
360
	 * Set module name.
361
	 *
362
	 * @param string $moduleName
363
	 *
364
	 * @return $this
365
	 */
366
	public function setModuleName($moduleName)
367
	{
368
		$this->moduleName = $moduleName;
369
		return $this;
370
	}
371
372
	/**
373
	 * Set params.
374
	 *
375
	 * @param array $params
376
	 */
377
	public function setParams($params)
378
	{
379
		$this->params = $params;
380
	}
381
382
	/**
383
	 * Add param.
384
	 *
385
	 * @param array $params
386
	 * @param mixed $key
387
	 * @param mixed $value
388
	 */
389
	public function addParams($key, $value)
390
	{
391
		$this->params[$key] = $value;
392
	}
393
394
	/**
395
	 * Get record model.
396
	 *
397
	 * @return \Vtiger_Record_Model
398
	 */
399
	public function getRecordModel()
400
	{
401
		return $this->recordModel;
402
	}
403
404
	/**
405
	 * Get module name.
406
	 *
407
	 * @return string
408
	 */
409
	public function getModuleName()
410
	{
411
		return $this->moduleName;
412
	}
413
414
	/**
415
	 * Get params.
416
	 *
417
	 * @return array Additional parameters
418
	 */
419
	public function getParams()
420
	{
421
		return $this->params;
422
	}
423
424
	/**
425
	 * Get param.
426
	 *
427
	 * @param string $key
428
	 *
429
	 * @return mixed
430
	 */
431
	public function getParam(string $key)
432
	{
433
		return $this->params[$key] ?? null;
434
	}
435
436
	/**
437
	 * Set exceptions.
438
	 *
439
	 * @param array $exceptions
440
	 */
441
	public function setExceptions(array $exceptions)
442
	{
443
		$this->exceptions = $exceptions;
444
		return $this;
445
	}
446
447
	/**
448
	 * @param string $name Event name
449
	 *
450
	 * @return array Handlers list
451
	 */
452
	public function getHandlers(string $name): array
453
	{
454
		$handlers = static::getByType($name, $this->moduleName);
455
		if ($this->exceptions['disableHandlers'] ?? null) {
456
			$handlers = array_intersect_key($handlers, array_flip(self::$mandatoryEventClass));
457
		} elseif ($disableHandlers = $this->exceptions['disableHandlerClasses'] ?? null) {
458
			foreach ($disableHandlers as $className) {
459
				if (isset($handlers[$className])) {
460
					unset($handlers[$className]);
461
				}
462
			}
463
		}
464
		return $handlers;
465
	}
466
467
	/**
468
	 * Trigger an event.
469
	 *
470
	 * @param string $name Event name
471
	 *
472
	 * @throws \App\Exceptions\AppException
473
	 */
474
	public function trigger(string $name)
475
	{
476
		foreach ($this->getHandlers($name) as $handler) {
477
			$this->triggerHandler($handler);
478
		}
479
	}
480
481
	/**
482
	 * Trigger handler.
483
	 *
484
	 * @param array $handler
485
	 *
486
	 * @throws \App\Exceptions\AppException
487
	 */
488
	public function triggerHandler(array $handler)
489
	{
490
		$className = $handler['handler_class'];
491
		$function = lcfirst($handler['event_name']);
492
		if (!method_exists($className, $function)) {
493
			Log::error("Handler not found, class: {$className} | {$function}");
494
			throw new \App\Exceptions\AppException('LBL_HANDLER_NOT_FOUND');
495
		}
496
		if (isset($this->handlers[$className])) {
497
			$handler = $this->handlers[$className];
498
		} else {
499
			$handler = $this->handlers[$className] = new $className();
500
		}
501
		return $handler->{$function}($this);
502
	}
503
}
504