commit
						5ef6744f32
					
				@ -0,0 +1,122 @@ | 
				
			||||
<?php | 
				
			||||
 | 
				
			||||
declare(strict_types=1); | 
				
			||||
 | 
				
			||||
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); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    public function logAttempt(bool $status, string $username, string $ip): void | 
				
			||||
    { | 
				
			||||
        $logDir = $this->logDir; | 
				
			||||
        if (!is_dir($logDir)) { | 
				
			||||
            mkdir($logDir, 0777, true); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        $date = new DateTime(); | 
				
			||||
        $logFilePrefix = $logDir . '/ids'; | 
				
			||||
        $logFile = $logFilePrefix . '.log'; | 
				
			||||
 | 
				
			||||
        // 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); | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        $statusText = $this->translator->trans($status ? 'succeeded' : 'failed'); | 
				
			||||
        $infoText = $this->translator->trans('info'); | 
				
			||||
        $clientText = $this->translator->trans('client'); | 
				
			||||
        $loginMessage = $this->translator->trans('Login %status% for username %username%', ['%status%' => $statusText, '%username%' => $username]); | 
				
			||||
 | 
				
			||||
        $logMessage = sprintf("[%s] [%s] [%s %s] %s\n", | 
				
			||||
            $date->format('Y-m-d H:i:s'), | 
				
			||||
            $infoText, | 
				
			||||
            $clientText, | 
				
			||||
            $ip, | 
				
			||||
            $loginMessage | 
				
			||||
        ); | 
				
			||||
 | 
				
			||||
        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'); | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue