-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathIpLogRepository.php
More file actions
133 lines (114 loc) · 4.25 KB
/
Copy pathIpLogRepository.php
File metadata and controls
133 lines (114 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<?php
declare(strict_types=1);
/*
* This file is part of the "typo3_login_warning" TYPO3 CMS extension.
*
* (c) 2025-2026 Konrad Michalik <km@move-elevator.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MoveElevator\Typo3LoginWarning\Domain\Repository;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use TYPO3\CMS\Core\Database\{Connection, ConnectionPool};
/**
* IpLogRepository.
*
* @author Konrad Michalik <km@move-elevator.de>
* @license GPL-2.0-or-later
*/
class IpLogRepository
{
private const TABLE_NAME = 'tx_typo3loginwarning_iplog';
public function __construct(private readonly ConnectionPool $connectionPool) {}
/**
* Registers a sighting of the given identifier and returns whether it was seen
* for the first time. Updates the timestamp first and only inserts when no row
* was touched, so concurrent logins racing on the same identifier cannot fail
* on the unique identifier_hash constraint.
*
* @throws Exception
*/
public function registerIdentifier(string $identifierHash): bool
{
$connection = $this->connectionPool->getConnectionForTable(self::TABLE_NAME);
$affectedRows = $connection->update(
self::TABLE_NAME,
['tstamp' => time()],
['identifier_hash' => $identifierHash],
);
if ($affectedRows > 0) {
return false;
}
try {
$connection->insert(self::TABLE_NAME, [
'identifier_hash' => $identifierHash,
'tstamp' => time(),
]);
} catch (UniqueConstraintViolationException) {
// A concurrent login registered the identifier in the meantime.
return false;
}
return true;
}
/**
* Entries with tstamp = 0 stem from extension versions that did not track the
* last sighting. Their real age is unknown, so they are excluded here and must
* be initialized via initializeMissingTimestamps() before any cleanup.
*
* @throws Exception
*/
public function countEntriesLastSeenBefore(int $timestamp): int
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME);
return (int) $queryBuilder
->count('uid')
->from(self::TABLE_NAME)
->where(
$queryBuilder->expr()->lt('tstamp', $queryBuilder->createNamedParameter($timestamp, Connection::PARAM_INT)),
$queryBuilder->expr()->gt('tstamp', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)),
)
->executeQuery()->fetchOne();
}
public function deleteEntriesLastSeenBefore(int $timestamp): int
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME);
return $queryBuilder
->delete(self::TABLE_NAME)
->where(
$queryBuilder->expr()->lt('tstamp', $queryBuilder->createNamedParameter($timestamp, Connection::PARAM_INT)),
$queryBuilder->expr()->gt('tstamp', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)),
)
->executeStatement();
}
/**
* @throws Exception
*/
public function countEntriesWithMissingTimestamp(): int
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME);
return (int) $queryBuilder
->count('uid')
->from(self::TABLE_NAME)
->where(
$queryBuilder->expr()->eq('tstamp', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)),
)
->executeQuery()->fetchOne();
}
/**
* Backfills the last-seen timestamp of legacy entries with the current time,
* granting them a full retention period before they become cleanup candidates.
*
* @throws Exception
*/
public function initializeMissingTimestamps(): int
{
$connection = $this->connectionPool->getConnectionForTable(self::TABLE_NAME);
return $connection->update(
self::TABLE_NAME,
['tstamp' => time()],
['tstamp' => 0],
);
}
}