|
|
|
@ -7,16 +7,20 @@ namespace Chamilo\CoreBundle\ServiceHelper; |
|
|
|
|
use Symfony\Component\HttpKernel\KernelInterface; |
|
|
|
|
use Symfony\Contracts\Translation\TranslatorInterface; |
|
|
|
|
use DateTime; |
|
|
|
|
use SplFileObject; |
|
|
|
|
|
|
|
|
|
class LoginAttemptLogger |
|
|
|
|
{ |
|
|
|
|
private string $logDir; |
|
|
|
|
private TranslatorInterface $translator; |
|
|
|
|
|
|
|
|
|
private int $maxLogs; |
|
|
|
|
|
|
|
|
|
public function __construct(KernelInterface $kernel, TranslatorInterface $translator) |
|
|
|
|
{ |
|
|
|
|
$this->logDir = $kernel->getLogDir() . '/ids'; |
|
|
|
|
$this->translator = $translator; |
|
|
|
|
$this->maxLogs = 90; // for how many iterations to keep log files |
|
|
|
|
|
|
|
|
|
if (!is_dir($this->logDir)) { |
|
|
|
|
mkdir($this->logDir, 0777, true); |
|
|
|
@ -31,15 +35,17 @@ class LoginAttemptLogger |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$date = new DateTime(); |
|
|
|
|
$logFilePrefix = $logDir . '/ids-' . $date->format('Y-m-d'); |
|
|
|
|
$logFilePrefix = $logDir . '/ids'; |
|
|
|
|
$logFile = $logFilePrefix . '.log'; |
|
|
|
|
|
|
|
|
|
if (file_exists($logFile) && $date->format('Y-m-d') !== date('Y-m-d', filemtime($logFile))) { |
|
|
|
|
$counter = 1; |
|
|
|
|
while (file_exists($logFilePrefix . '.' . $counter . '.log')) { |
|
|
|
|
$counter++; |
|
|
|
|
// Most of the time, there will be a previous log file |
|
|
|
|
if (file_exists($logFile)) { |
|
|
|
|
// Read the last line of the existing log file to determine if we need rotation |
|
|
|
|
$lastLine = $this->_readLastLine($logFile); |
|
|
|
|
if (!$this->_checkDateIsToday($lastLine)) { |
|
|
|
|
// The last line's date is not today, so we need to rotate |
|
|
|
|
$this->_rotateLogFiles($logFile, $this->maxLogs); |
|
|
|
|
} |
|
|
|
|
rename($logFile, $logFilePrefix . '.' . $counter . '.log'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$statusText = $this->translator->trans($status ? 'succeeded' : 'failed'); |
|
|
|
@ -57,4 +63,60 @@ class LoginAttemptLogger |
|
|
|
|
|
|
|
|
|
file_put_contents($logFile, $logMessage, FILE_APPEND | LOCK_EX); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Efficiently read the last line of the provided file, or an empty string |
|
|
|
|
* @param string $logFilePath |
|
|
|
|
* @return string |
|
|
|
|
*/ |
|
|
|
|
private function _readLastLine(string $logFilePath): string |
|
|
|
|
{ |
|
|
|
|
$fileObject = new SplFileObject($logFilePath, 'r'); |
|
|
|
|
$fileObject->seek(PHP_INT_MAX); |
|
|
|
|
$fileObject->seek($fileObject->key() - 1); |
|
|
|
|
$line = $fileObject->current(); |
|
|
|
|
if (empty($line)) { |
|
|
|
|
return ''; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return $line; |
|
|
|
|
} |
|
|
|
|
/** |
|
|
|
|
* Check if the date in a log line is same as today |
|
|
|
|
* @param string $line A line of type "[2024-03-01 09:44:57] [info] [client 127.0.0.1] Some text" |
|
|
|
|
*/ |
|
|
|
|
private function _checkDateIsToday(string $line): bool |
|
|
|
|
{ |
|
|
|
|
$date = new DateTime(); |
|
|
|
|
$today = $date->format('Y-m-d'); |
|
|
|
|
$matches = []; |
|
|
|
|
if (!empty($line) && preg_match('/\[(\d{4}-\d{2}-\d{2})\s.*/', $line, $matches)) { |
|
|
|
|
if (0 === strcmp($matches[1], $today)) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Rotate log files |
|
|
|
|
* @param string $baseLogFile |
|
|
|
|
* @param int $maxLogs |
|
|
|
|
* @return void |
|
|
|
|
*/ |
|
|
|
|
private function _rotateLogFiles(string $baseLogFile, int $maxLogs): void |
|
|
|
|
{ |
|
|
|
|
for ($i = $maxLogs; $i > 0; $i--) { |
|
|
|
|
$oldLog = $baseLogFile.'.'.$i; |
|
|
|
|
$newLog = $baseLogFile.'.'.($i + 1); |
|
|
|
|
if (file_exists($oldLog)) { |
|
|
|
|
if (file_exists($newLog)) { |
|
|
|
|
unlink($newLog); |
|
|
|
|
} |
|
|
|
|
rename($oldLog, $newLog); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
rename($baseLogFile, $baseLogFile.'.1'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|