Skip to content

Joomla 5 and 6 compability #5

Description

@LordHelmchen666

Could you please review this code and check if everything is correct? Should work with J4, 5 and 6.

<?php
/*
 * @copyright	Copyright (C) 2014 Markus Neubauer. All rights reserved.
 * @license	http://www.gnu.org/licenses/agpl-3.0.html GNU/AGPL
 * @github	https://github.com/marneu/joomlatic/tree/master/syslogauthlog
 * @homepage	http://www.std-soft.com/index.php/hm-service/81-c-std-service-code/9-joomla-plugin-syslogauthlog
 * @version	1.7.1 (ported for Joomla 4+/6 compatibility)
 */

defined('_JEXEC') or die();

use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Factory;
use Psr\Log\LogLevel;

/*
 * Compatibility shims for removed legacy classes in Joomla 4+/6.
 * These provide minimal implementations so the plugin can continue
 * to create JLogEntry objects and write to syslog using PHP's syslog().
 */

// Ensure JFactory alias exists for older code paths that use JFactory
if (!class_exists('JFactory') && class_exists(\Joomla\CMS\Factory::class)) {
	class_alias(\Joomla\CMS\Factory::class, 'JFactory');
}

// Minimal JLog class (constants + add fallback)
if (!class_exists('JLog')) {
	class JLog {
		const EMERGENCY = LOG_EMERG;
		const ALERT     = LOG_ALERT;
		const CRITICAL  = LOG_CRIT;
		const ERROR     = LOG_ERR;
		const WARNING   = LOG_WARNING;
		const NOTICE    = LOG_NOTICE;
		const INFO      = LOG_INFO;
		const DEBUG     = LOG_DEBUG;

		public static function add($message, $priority = LOG_INFO, $category = '')
		{
			// write directly to syslog
			$prefixed = ($category ? "[$category] " : '') . $message;
			syslog($priority, $prefixed);
		}
	}
}

// Minimal JLogEntry container
if (!class_exists('JLogEntry')) {
	class JLogEntry {
		public $message;
		public $priority;
		public $category;

		public function __construct($message, $priority = LOG_INFO, $category = '')
		{
			$this->message  = $message;
			$this->priority = $priority;
			$this->category = $category;
		}
	}
}

// Minimal syslog logger used by the plugin (replaces legacy JLogLoggerSyslog)
if (!class_exists('JLogLoggerSyslog')) {
	class JLogLoggerSyslog {
		protected $options = array();
		protected $opened  = false;

		public function __construct($options = array())
		{
			$defaults = array(
				'sys_ident'      => 'jauthlog',
				'sys_add_pid'    => true,
				'sys_use_stderr' => false,
				'sys_facility'   => LOG_AUTH
			);
			$this->options = array_merge($defaults, (array) $options);

			$flags = 0;
			if (!empty($this->options['sys_add_pid'])) {
				$flags |= LOG_PID;
			}
			// LOG_PERROR may not exist on all systems, guard it
			if (!empty($this->options['sys_use_stderr']) && defined('LOG_PERROR')) {
				$flags |= LOG_PERROR;
			}

			@openlog($this->options['sys_ident'], $flags, $this->options['sys_facility']);
			$this->opened = true;
		}

		public function addEntry($entry)
		{
			if (!$this->opened) {
				@openlog($this->options['sys_ident'], LOG_PID, $this->options['sys_facility']);
				$this->opened = true;
			}

			// $entry->priority expected to be a LOG_* constant
			@syslog($entry->priority, $entry->message);
		}

		public function __destruct()
		{
			if ($this->opened) {
				@closelog();
			}
		}
	}
}


/**
 * Plugin class
 */
class PlgSystemSyslogAuthLog extends CMSPlugin {

	/**
	* Load the language file on instantiation.
	*
	* @var    boolean
	* @since  3.1
	*/
	protected $autoloadLanguage = true;

	private $format = '{PRIORITY} {EVENT} {USERNAME} {ADMIN} {MESSAGE} from {CLIENTIP}';

	private $syslog_options = array(
		'sys_ident' 	 => 'jauthlog',
		'sys_add_pid' 	 => true,
		'sys_use_stderr' => false,
		'sys_facility' 	 => LOG_AUTH
	);
	private $syslog;

	// ranges known as private or virtual IPs (unused in public internet)
	private $ignore_ip = array (
		array('0.0.0.0','2.255.255.255'),
		array('10.0.0.0','10.255.255.255'),
		array('127.0.0.0','127.255.255.255'),
		array('169.254.0.0','169.254.255.255'),
		array('172.16.0.0','172.31.255.255'),
		array('192.0.0.0','192.0.0.255'),
		array('192.0.2.0','192.0.2.255'),
		array('192.88.99.0','192.88.99.255'),
		array('192.168.0.0','192.168.255.255'),
		array('198.18.0.0','198.19.255.255'),
		array('198.51.100.0','198.51.100.255'),
		array('203.0.113.0','203.0.113.255'),
		array('224.0.0.0','255.255.255.255')
	);

	public $priorities = array(
		JLog::EMERGENCY => 'EMERG',
		JLog::ALERT     => 'ALERT',
		JLog::CRITICAL  => 'CRIT',
		JLog::ERROR     => 'ERR',
		JLog::WARNING   => 'WARNING',
		JLog::NOTICE    => 'NOTICE',
		JLog::INFO      => 'INFO',
		JLog::DEBUG     => 'DEBUG'
	);

	private $priority = JLog::INFO;

	private $field = array( 'MESSAGE' => '' );

	private $version = JVERSION;

	public function __construct(&$subject, $config)
	{
		parent::__construct($subject, $config);

		// Note: removed an unused legacy JApplicationHelper::getHash() call.
		// If you need a hash here for other reasons, reimplement with the appropriate API.
	}

	private function check_ip($ip)
	{
		if (!empty($ip) && ip2long($ip) != -1) {
			foreach ($this->ignore_ip as $r) {
				$min = ip2long($r[0]);
				$max = ip2long($r[1]);
				if ((ip2long($ip) >= $min) && (ip2long($ip) <= $max)) return false;
			}
			return true;
		} else {
			return false;
		}
	}


	private function getAddr()
	{
		if (isset($_SERVER["HTTP_CLIENT_IP"]) && $this->check_ip($_SERVER["HTTP_CLIENT_IP"])) {
			return $_SERVER["HTTP_CLIENT_IP"];
		}
		if ( isset($_SERVER["HTTP_X_FORWARDED_FOR"]) ) {
			foreach (explode(",",$_SERVER["HTTP_X_FORWARDED_FOR"]) as $ip) {
				if ($this->check_ip(trim($ip))) {
					return $ip;
				}
			}
		}
		if ( isset($_SERVER["HTTP_X_FORWARDED"]) && $this->check_ip($_SERVER["HTTP_X_FORWARDED"])) {
			return $_SERVER["HTTP_X_FORWARDED"];
		} elseif ( isset($_SERVER["HTTP_FORWARDED_FOR"]) && $this->check_ip($_SERVER["HTTP_FORWARDED_FOR"])) {
			return $_SERVER["HTTP_FORWARDED_FOR"];
		} elseif ( isset($_SERVER["HTTP_FORWARDED"]) && $this->check_ip($_SERVER["HTTP_FORWARDED"])) {
			return $_SERVER["HTTP_FORWARDED"];
		} else {
			return $_SERVER["REMOTE_ADDR"];
		}
	}


	private function log() {

		if (Factory::getApplication()->isClient('administrator')) {
			$this->field['ADMIN'] = 'ADMIN';
		}
		else {
			if ( $this->params->def('type', 1) == 1 ) return;
			$this->field['ADMIN'] = '';
		}

		$this->field['CLIENTIP'] = $this->getAddr();

		$this->field['PRIORITY'] = $this->priorities[$this->priority];

		// Fill in field data for the line.
		$message = $this->format;

		foreach ($this->field as $tmp => $val)
		{
			$message = str_replace('{' . $tmp . '}', $val, $message);
		}

		// setup the new entry for syslog.
		$jLogEntry = new JLogEntry($message, $this->priority, getenv('USER'));

		// connect to syslog (plugin supports Joomla 3..6 behaviour previously)
		if ( substr( $this->version, 0, 1) == '3'
		  || substr( $this->version, 0, 1) == '4'
		  || substr( $this->version, 0, 1) == '5'
		  || substr( $this->version, 0, 1) == '6'
		)
		{
			$this->syslog = new JLogLoggerSyslog($this->syslog_options);
		} else {
			die ("Unsupported Joomla version for plg syslogauth.");
		}
		// Write the new entry to syslog.
		$this->syslog->addEntry($jLogEntry);

	}

	public function onUserAfterLogin($options)
	{ // JAuthenticationResponse array

		if ( $this->params->def('mode', 1) != 0 ) return;
		if ( $this->params->def('event', 1) == 2 ) return;
		$this->field['EVENT'] = 'login';
		$this->field['USERNAME'] = isset($options['user']->username) ? $options['user']->username : (isset($options['username']) ? $options['username'] : 'UNKNOWN');
		$this->log();
	}

	public function onUserLoginFailure($response)
	{
		// do not log successful logins (status may be a string or a legacy constant)
		$success = false;
		if (isset($response['status'])) {
			if (is_string($response['status']) && strtoupper($response['status']) === 'SUCCESS') {
				$success = true;
			} elseif (class_exists('JAuthentication') && defined('JAuthentication::STATUS_SUCCESS')) {
				// If legacy constant exists, attempt to compare (guarded)
				try {
					if ($response['status'] == JAuthentication::STATUS_SUCCESS) $success = true;
				} catch (\Throwable $e) {
					// ignore
				}
			}
		}
		if ($success) return;

		// log only if configured to do so
		if ( $this->params->def('event', 1) == 2 ) return;

		$this->field['MESSAGE'] = isset($response['error_message']) ? $response['error_message'] : '';
		$this->priority = JLog::WARNING;
		$this->field['EVENT'] = 'login';
		if ($this->params->get('log_username', 0)) {
			$this->field['USERNAME'] = isset($response['username']) ? $response['username'] : 'UNKNOWN';
		} else {
			$this->field['USERNAME'] = 'UNKNOWN';
		}
		$this->log();
	}

	public function onUserAfterLogout($options)
	{

		if ( $this->params->def('mode', 1) != 0 ) return;
		if ( $this->params->def('event', 1) == 1 ) return;
		$this->field['EVENT'] = 'logout';
		$this->field['USERNAME'] = isset($options['username']) ? $options['username'] : 'UNKNOWN';
		$this->log();
	}

	public function onUserLogoutFailure($parameters)
	{

		if ( $this->params->def('event', 1) == 1 ) return;
		$this->priority = JLog::WARNING;
		$this->field['EVENT'] = 'logout';
		$this->field['USERNAME'] = isset($parameters['username']) ? $parameters['username'] : 'UNKNOWN';
		$this->field['MESSAGE'] = 'failure';
		$this->log();
	}

}
?>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions