|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
namespace App\Infra\Log; |
|
6
|
|
|
|
|
7
|
|
|
use App\Entity\AccessLog; |
|
8
|
|
|
use DeviceDetector\DeviceDetector; |
|
9
|
|
|
use Doctrine\ORM\EntityManager; |
|
10
|
|
|
|
|
11
|
|
|
class AccessLogger |
|
12
|
|
|
{ |
|
13
|
|
|
public const TYPE_LOGIN_SUCCESS = 'success'; |
|
14
|
|
|
public const TYPE_LOGIN_FAILURE = 'fail'; |
|
15
|
|
|
public const TYPE_LOGIN_FAILURE_EXPIRY = 'fail.expiry'; |
|
16
|
|
|
public const TYPE_LOGIN_FAILURE_PAYMENT_ISSUE = 'fail.payment'; |
|
17
|
|
|
public const TYPE_LOGIN_FAILURE_CREDENTIALS = 'fail.credentials'; |
|
18
|
|
|
public const TYPE_LOGIN_FAILURE_NO_ACCESS = 'fail.no_access'; |
|
19
|
|
|
|
|
20
|
|
|
public const SUPPORTED_TYPES = [ |
|
21
|
|
|
self::TYPE_LOGIN_SUCCESS, |
|
22
|
|
|
self::TYPE_LOGIN_FAILURE, |
|
23
|
|
|
self::TYPE_LOGIN_FAILURE_CREDENTIALS, |
|
24
|
|
|
self::TYPE_LOGIN_FAILURE_EXPIRY, |
|
25
|
|
|
self::TYPE_LOGIN_FAILURE_PAYMENT_ISSUE, |
|
26
|
|
|
self::TYPE_LOGIN_FAILURE_NO_ACCESS, |
|
27
|
|
|
]; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* @var EntityManager |
|
31
|
|
|
*/ |
|
32
|
|
|
private $em; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* Device detector caches. |
|
36
|
|
|
* |
|
37
|
|
|
* @var array<string, DeviceDetector> |
|
38
|
|
|
*/ |
|
39
|
|
|
private $ddCache; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* Whether user agent detection is enabled. |
|
43
|
|
|
* |
|
44
|
|
|
* @var bool |
|
45
|
|
|
*/ |
|
46
|
|
|
private $uaDetectionEnabled; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* @param bool $uaDetectionEnabled Whether user agent detection is enabled |
|
50
|
|
|
*/ |
|
51
|
|
|
public function __construct(EntityManager $entityManager, $uaDetectionEnabled = true) |
|
52
|
|
|
{ |
|
53
|
|
|
$this->em = $entityManager; |
|
54
|
|
|
$this->uaDetectionEnabled = $uaDetectionEnabled; |
|
55
|
|
|
$this->ddCache = []; |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* @throws \Throwable |
|
60
|
|
|
*/ |
|
61
|
|
|
public function log(string $type, string $email, ?string $language, ?string $ip, ?string $userAgent): void |
|
62
|
|
|
{ |
|
63
|
|
|
$browserInfo = $this->getBrowserInfo($this->uaDetectionEnabled ? $userAgent : null); |
|
64
|
|
|
['os' => $os, 'browser' => $browser, 'version' => $browserVersion, 'type' => $deviceType] = $browserInfo; |
|
65
|
|
|
$accessLog = new AccessLog($type, $email, $language, $ip, $userAgent, $browser, $browserVersion, $os, $deviceType); |
|
66
|
|
|
try { |
|
67
|
|
|
$this->em->persist($accessLog); |
|
68
|
|
|
$this->em->flush(); |
|
69
|
|
|
} catch (\Throwable $e) { |
|
70
|
|
|
throw $e; |
|
71
|
|
|
} |
|
72
|
|
|
} |
|
73
|
|
|
|
|
74
|
|
|
/** |
|
75
|
|
|
* @return array<string, mixed> |
|
76
|
|
|
*/ |
|
77
|
|
|
public function getBrowserInfo(?string $userAgent): array |
|
78
|
|
|
{ |
|
79
|
|
|
if ($userAgent === null) { |
|
80
|
|
|
return [ |
|
81
|
|
|
'os' => null, |
|
82
|
|
|
'browser' => null, |
|
83
|
|
|
'version' => null, |
|
84
|
|
|
'type' => null, |
|
85
|
|
|
]; |
|
86
|
|
|
} |
|
87
|
|
|
|
|
88
|
|
|
$dd = $this->getDeviceDetector($userAgent); |
|
89
|
|
|
$dd->skipBotDetection(); |
|
90
|
|
|
$dd->parse(); |
|
91
|
|
|
|
|
92
|
|
|
return [ |
|
93
|
|
|
'os' => $dd->getOs('short_name'), |
|
94
|
|
|
'browser' => $dd->getClient('name'), |
|
95
|
|
|
'version' => $dd->getClient('version'), |
|
96
|
|
|
'type' => $dd->getDeviceName(), |
|
97
|
|
|
]; |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
/** |
|
101
|
|
|
* Basic in-memory caching, should be fine for AccessLogger in context. |
|
102
|
|
|
*/ |
|
103
|
|
|
private function getDeviceDetector(string $userAgent): DeviceDetector |
|
104
|
|
|
{ |
|
105
|
|
|
$agentKey = md5($userAgent); |
|
106
|
|
|
if (!isset($this->ddCache[$agentKey])) { |
|
107
|
|
|
$this->ddCache[$agentKey] = new DeviceDetector($userAgent); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
return $this->ddCache[$agentKey]; |
|
111
|
|
|
} |
|
112
|
|
|
} |
|
113
|
|
|
|