Ecodev /
felix
| 1 | <?php |
||||
| 2 | |||||
| 3 | declare(strict_types=1); |
||||
| 4 | |||||
| 5 | namespace Ecodev\Felix\Repository\Traits; |
||||
| 6 | |||||
| 7 | use Cake\Chronos\Chronos; |
||||
| 8 | use Doctrine\DBAL\Connection; |
||||
| 9 | use Doctrine\ORM\EntityManager; |
||||
| 10 | use Doctrine\ORM\QueryBuilder; |
||||
| 11 | use Ecodev\Felix\Model\User; |
||||
| 12 | use Ecodev\Felix\Repository\LogRepository as LogRepositoryInterface; |
||||
| 13 | use Laminas\Log\Logger; |
||||
| 14 | |||||
| 15 | trait LogRepository |
||||
| 16 | { |
||||
| 17 | /** |
||||
| 18 | * @return EntityManager |
||||
| 19 | */ |
||||
| 20 | abstract protected function getEntityManager(); |
||||
| 21 | |||||
| 22 | /** |
||||
| 23 | * Creates a new QueryBuilder instance that is prepopulated for this entity name. |
||||
| 24 | * |
||||
| 25 | * @param string $alias |
||||
| 26 | * @param string $indexBy the index for the from |
||||
| 27 | * |
||||
| 28 | * @return QueryBuilder |
||||
| 29 | */ |
||||
| 30 | abstract public function createQueryBuilder($alias, $indexBy = null); |
||||
| 31 | |||||
| 32 | /** |
||||
| 33 | * This should NOT be called directly, instead use `_log()` to log stuff. |
||||
| 34 | */ |
||||
| 35 | public function log(array $event): void |
||||
| 36 | { |
||||
| 37 | $event['creation_date'] = Chronos::instance($event['timestamp'])->toIso8601String(); |
||||
| 38 | $event['extra'] = json_encode($event['extra'], JSON_THROW_ON_ERROR); |
||||
| 39 | unset($event['timestamp'], $event['priorityName'], $event['login']); |
||||
| 40 | |||||
| 41 | $this->getEntityManager()->getConnection()->insert('log', $event); |
||||
| 42 | } |
||||
| 43 | |||||
| 44 | /** |
||||
| 45 | * Returns whether the current IP often failed to login. |
||||
| 46 | */ |
||||
| 47 | public function loginFailedOften(): bool |
||||
| 48 | { |
||||
| 49 | return $this->failedOften(LogRepositoryInterface::LOGIN, LogRepositoryInterface::LOGIN_FAILED); |
||||
| 50 | } |
||||
| 51 | |||||
| 52 | public function updatePasswordFailedOften(): bool |
||||
| 53 | { |
||||
| 54 | return $this->failedOften(LogRepositoryInterface::UPDATE_PASSWORD, LogRepositoryInterface::UPDATE_PASSWORD_FAILED); |
||||
| 55 | } |
||||
| 56 | |||||
| 57 | public function requestPasswordResetOften(): bool |
||||
| 58 | { |
||||
| 59 | return $this->failedOften(LogRepositoryInterface::UPDATE_PASSWORD, LogRepositoryInterface::REQUEST_PASSWORD_RESET, 10); |
||||
| 60 | } |
||||
| 61 | |||||
| 62 | public function registerOften(): bool |
||||
| 63 | { |
||||
| 64 | return $this->failedOften(LogRepositoryInterface::REGISTER_CONFIRM, LogRepositoryInterface::REGISTER, 10); |
||||
| 65 | } |
||||
| 66 | |||||
| 67 | private function failedOften(string $success, string $failed, int $maxFailureCount = 20): bool |
||||
| 68 | { |
||||
| 69 | if (PHP_SAPI === 'cli') { |
||||
| 70 | $ip = !empty(getenv('REMOTE_ADDR')) ? getenv('REMOTE_ADDR') : 'script'; |
||||
| 71 | } else { |
||||
| 72 | $ip = $_SERVER['REMOTE_ADDR'] ?? ''; |
||||
| 73 | } |
||||
| 74 | |||||
| 75 | $select = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||||
| 76 | ->select('message') |
||||
| 77 | ->from('log') |
||||
| 78 | ->andWhere('priority = :priority') |
||||
| 79 | ->setParameter('priority', Logger::INFO) |
||||
| 80 | ->andWhere('message IN (:message)') |
||||
| 81 | ->setParameter('message', [$success, $failed], Connection::PARAM_STR_ARRAY) |
||||
| 82 | ->andWhere('creation_date > DATE_SUB(NOW(), INTERVAL 30 MINUTE)') |
||||
| 83 | ->andWhere('ip = :ip') |
||||
| 84 | ->setParameter('ip', $ip) |
||||
| 85 | ->orderBy('id', 'DESC'); |
||||
| 86 | |||||
| 87 | $events = $select->execute()->fetchFirstColumn(); |
||||
|
0 ignored issues
–
show
|
|||||
| 88 | |||||
| 89 | // Goes from present to past and count failure, until the last time we succeeded logging in |
||||
| 90 | $failureCount = 0; |
||||
| 91 | foreach ($events as $event) { |
||||
| 92 | if ($event === $success) { |
||||
| 93 | break; |
||||
| 94 | } |
||||
| 95 | ++$failureCount; |
||||
| 96 | } |
||||
| 97 | |||||
| 98 | return $failureCount > $maxFailureCount; |
||||
| 99 | } |
||||
| 100 | |||||
| 101 | /** |
||||
| 102 | * Delete log entries which are errors/warnings and older than one month |
||||
| 103 | * We always keep Logger::INFO level because we use it for statistics. |
||||
| 104 | * |
||||
| 105 | * @return int the count deleted logs |
||||
| 106 | */ |
||||
| 107 | public function deleteOldLogs(): int |
||||
| 108 | { |
||||
| 109 | $connection = $this->getEntityManager()->getConnection(); |
||||
| 110 | $query = $connection->createQueryBuilder() |
||||
| 111 | ->delete('log') |
||||
| 112 | ->andWhere('log.priority != :priority OR message IN (:message)') |
||||
| 113 | ->setParameter('priority', Logger::INFO) |
||||
| 114 | ->setParameter('message', [ |
||||
| 115 | LogRepositoryInterface::LOGIN_FAILED, |
||||
| 116 | LogRepositoryInterface::REQUEST_PASSWORD_RESET, |
||||
| 117 | LogRepositoryInterface::REGISTER, |
||||
| 118 | ], Connection::PARAM_STR_ARRAY) |
||||
| 119 | ->andWhere('log.creation_date < DATE_SUB(NOW(), INTERVAL 1 MONTH)'); |
||||
| 120 | |||||
| 121 | $connection->executeStatement('LOCK TABLES `log` WRITE;'); |
||||
| 122 | $count = $query->execute(); |
||||
|
0 ignored issues
–
show
The function
Doctrine\DBAL\Query\QueryBuilder::execute() has been deprecated: Use {@see executeQuery()} or {@see executeStatement()} instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. Loading history...
|
|||||
| 123 | $connection->executeStatement('UNLOCK TABLES;'); |
||||
| 124 | |||||
| 125 | return $count; |
||||
|
1 ignored issue
–
show
|
|||||
| 126 | } |
||||
| 127 | |||||
| 128 | public function getLoginDate(User $user, bool $first): ?Chronos |
||||
| 129 | { |
||||
| 130 | $qb = $this->createQueryBuilder('log') |
||||
| 131 | ->select('log.creationDate') |
||||
| 132 | ->andWhere('log.creator = :user') |
||||
| 133 | ->andWhere('log.message = :message') |
||||
| 134 | ->setParameter('user', $user) |
||||
| 135 | ->setParameter('message', LogRepositoryInterface::LOGIN) |
||||
| 136 | ->addOrderBy('log.creationDate', $first ? 'ASC' : 'DESC'); |
||||
| 137 | |||||
| 138 | $result = $qb->getQuery()->setMaxResults(1)->getOneOrNullResult(); |
||||
| 139 | |||||
| 140 | return $result['creationDate'] ?? null; |
||||
| 141 | } |
||||
| 142 | } |
||||
| 143 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.