<?php
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Factory;
use Joomla\CMS\Router\Route;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Form\Form;
use Joomla\Utilities\ArrayHelper;
JLoader::register('JFormFieldLdapTestButton', JPATH_PLUGINS . '/authentication/ldapauth/fields/ldaptestbutton.php');
class PlgAuthenticationLdapauth extends CMSPlugin
{
public function onUserAuthenticate($credentials, $options, &$response)
{
// Load plugin parameters
$params = $this->params;
// LDAP connection parameters
$ldaphosts = [
$params->get('ldaphost1'),
$params->get('ldaphost2'),
$params->get('ldaphost3')
];
$port = $params->get('portdefault', '389') === 'custom' ? $params->get('customport', 389) : $params->get('portdefault', 389);
$ldapv3 = $params->get('ldapv3', 0);
$connectionsecurity = $params->get('connectionsecurity', 'none');
$followreferrals = $params->get('followreferrals', 0);
$authorisationmethod = $params->get('authorisationmethod', 'binddirectly');
$basedn = $params->get('basedn');
$searchstring = $params->get('searchstring');
$usersdn = $params->get('usersdn');
$connectusername = $params->get('connectusername');
$connectpassword = $params->get('connectpassword');
$mapfullname = $params->get('mapfullname');
$mapemail = $params->get('mapemail');
$mapuserid = $params->get('mapuserid');
$debug = $params->get('debug', 0);
$gssapi = $params->get('gssapi', 0);
$sasl = $params->get('sasl', 0);
foreach ($ldaphosts as $ldaphost) {
if (empty($ldaphost)) {
continue;
}
// Determine LDAP protocol
$protocol = 'ldap://';
if ($connectionsecurity === 'ssl') {
$protocol = 'ldaps://';
}
// Construct the full hostname with protocol
$fullLdaphost = $protocol . $ldaphost;
// Initialize LDAP connection
$ldapconn = ldap_connect($fullLdaphost, $port);
if ($ldapconn) {
// Set LDAP options
ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, $ldapv3 ? 3 : 2);
ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, $followreferrals ? 1 : 0);
ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, 10); // 10 seconds timeout
ldap_set_option($ldapconn, LDAP_OPT_DEBUG_LEVEL, 7); // Enable debug level
// Connection security (STARTTLS)
if ($connectionsecurity === 'starttls') {
if (!ldap_start_tls($ldapconn)) {
$response->status = JAuthentication::STATUS_FAILURE;
$response->error_message = 'Failed to start TLS connection.';
continue;
}
}
// Binding to LDAP
if ($debug) {
Log::add('Attempting to bind with username: ' . $connectusername, Log::DEBUG, 'ldapauth');
}
$bind = @ldap_bind($ldapconn, $connectusername, $connectpassword);
if ($bind) {
if ($debug) {
Log::add('Bind successful.', Log::DEBUG, 'ldapauth');
}
// Search for the user
$filter = "({$searchstring}={$credentials['username']})";
if ($debug) {
Log::add('LDAP search filter: ' . $filter, Log::DEBUG, 'ldapauth');
}
$result = ldap_search($ldapconn, $basedn, $filter);
if ($result) {
$entries = ldap_get_entries($ldapconn, $result);
if ($entries['count'] > 0) {
$userdn = $entries[0]['dn'];
// Attempt to bind as the user
if ($debug) {
Log::add('Attempting to bind as user: ' . $userdn, Log::DEBUG, 'ldapauth');
}
$bindUser = @ldap_bind($ldapconn, $userdn, $credentials['password']);
if ($bindUser) {
if ($debug) {
Log::add('User bind successful.', Log::DEBUG, 'ldapauth');
}
// Authentication successful
$response->status = JAuthentication::STATUS_SUCCESS;
$response->email = ArrayHelper::getValue($entries[0], $mapemail . '.0', '');
$response->fullname = ArrayHelper::getValue($entries[0], $mapfullname . '.0', '');
$response->username = $credentials['username'];
return;
} else {
// Authentication failed
$response->status = JAuthentication::STATUS_FAILURE;
$response->error_message = 'Invalid username or password.';
if ($debug) {
Log::add('User bind failed. Invalid username or password.', Log::DEBUG, 'ldapauth');
Log::add('LDAP Error: ' . ldap_error($ldapconn), Log::DEBUG, 'ldapauth');
}
}
} else {
// User not found
$response->status = JAuthentication::STATUS_FAILURE;
$response->error_message = 'User not found.';
if ($debug) {
Log::add('LDAP search returned no entries.', Log::DEBUG, 'ldapauth');
}
}
} else {
// Search failed
$response->status = JAuthentication::STATUS_FAILURE;
$response->error_message = 'LDAP search failed.';
if ($debug) {
Log::add('LDAP search failed.', Log::DEBUG, 'ldapauth');
Log::add('LDAP Error: ' . ldap_error($ldapconn), Log::DEBUG, 'ldapauth');
}
}
} else {
// Could not bind to LDAP
$response->status = JAuthentication::STATUS_FAILURE;
$response->error_message = 'Could not bind to LDAP server.';
if ($debug) {
Log::add('LDAP bind failed.', Log::DEBUG, 'ldapauth');
Log::add('LDAP Error: ' . ldap_error($ldapconn), Log::DEBUG, 'ldapauth');
}
}
ldap_unbind($ldapconn);
} else {
// Could not connect to LDAP
$response->status = JAuthentication::STATUS_FAILURE;
$response->error_message = 'Could not connect to LDAP server.';
if ($debug) {
Log::add('Could not connect to LDAP server: ' . $fullLdaphost, Log::DEBUG, 'ldapauth');
}
}
}
// Logging for debugging
if ($debug) {
Log::addLogger(
array('text_file' => 'ldapauth.debug.php'),
Log::ALL,
array('ldapauth')
);
Log::add('LDAP Hosts: ' . implode(', ', $ldaphosts), Log::DEBUG, 'ldapauth');
Log::add('Port: ' . $port, Log::DEBUG, 'ldapauth');
Log::add('Using LDAP V3: ' . ($ldapv3 ? 'Yes' : 'No'), Log::DEBUG, 'ldapauth');
Log::add('Connection Security: ' . $connectionsecurity, Log::DEBUG, 'ldapauth');
Log::add('Follow Referrals: ' . ($followreferrals ? 'Yes' : 'No'), Log::DEBUG, 'ldapauth');
Log::add('Authorisation Method: ' . $authorisationmethod, Log::DEBUG, 'ldapauth');
Log::add('Base DN: ' . $basedn, Log::DEBUG, 'ldapauth');
Log::add('Search String: ' . $searchstring, Log::DEBUG, 'ldapauth');
Log::add('User DN: ' . $usersdn, Log::DEBUG, 'ldapauth');
Log::add('Connect Username: ' . $connectusername, Log::DEBUG, 'ldapauth');
Log::add('Mapping Full Name: ' . $mapfullname, Log::DEBUG, 'ldapauth');
Log::add('Mapping Email: ' . $mapemail, Log::DEBUG, 'ldapauth');
Log::add('Mapping User ID: ' . $mapuserid, Log::DEBUG, 'ldapauth');
Log::add('GSSAPI: ' . ($gssapi ? 'Yes' : 'No'), Log::DEBUG, 'ldapauth');
Log::add('SASL: ' . ($sasl ? 'Yes' : 'No'), Log::DEBUG, 'ldapauth');
}
}
// Function to handle LDAP test
public function testLdapAuthentication()
{
$app = Factory::getApplication();
$input = $app->input;
$testUsername = $input->getString('test_username');
$testPassword = $input->getString('test_password');
if (empty($testUsername) || empty($testPassword)) {
$app->enqueueMessage(Text::_('Please enter both username and password for testing.'), 'warning');
return;
}
$response = new stdClass();
$this->onUserAuthenticate(['username' => $testUsername, 'password' => $testPassword], [], $response);
if ($response->status === JAuthentication::STATUS_SUCCESS) {
$app->enqueueMessage(Text::_('LDAP authentication successful'), 'message');
$input->set('test_result', 'LDAP authentication successful');
} else {
$app->enqueueMessage(Text::_('LDAP authentication failed: ' . $response->error_message), 'error');
$input->set('test_result', 'LDAP authentication failed: ' . $response->error_message);
}
}
// Add HTML and JavaScript for the configuration form
public function onAfterRender()
{
$app = Factory::getApplication();
if ($app->isClient('administrator')) {
$input = $app->input;
$option = $input->getCmd('option');
$view = $input->getCmd('view');
$layout = $input->getCmd('layout');
$plugin = $input->getCmd('plugin');
if ($option == 'com_plugins' && $view == 'plugin' && $layout == 'edit' && $input->getInt('id') == $this->params->get('id')) {
$doc = $app->getDocument();
// Include Bootstrap CDN
$doc->addStyleSheet('https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css');
$doc->addScript('https://releases.jquery.com/git/jquery-3.x-git.slim.min.js');
$doc->addScript('https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js');
// Output the form
$html = '
<div class="row-fluid">
<div class="span10 form-horizontal">
' . HTMLHelper::_('bootstrap.startTabSet', 'myTab', ['active' => 'general']) . '
' . HTMLHelper::_('bootstrap.addTab', 'myTab', 'general', Text::_('General', true)) . '
<div class="row-fluid">
<div class="span6">';
foreach ($this->params->getFieldset('general') as $field) {
$html .= '
<div class="control-group">
<div class="control-label">' . $field->label . '</div>
<div class="controls">' . $field->input . '</div>
</div>';
}
$html .= '
</div>
</div>
' . HTMLHelper::_('bootstrap.endTab') . '
' . HTMLHelper::_('bootstrap.addTab', 'myTab', 'test', Text::_('Test LDAP Settings', true)) . '
<div class="row-fluid">
<div class="span6">';
foreach ($this->params->getFieldset('test') as $field) {
$html .= '
<div class="control-group">
<div class="control-label">' . $field->label . '</div>
<div class="controls">' . $field->input . '</div>
</div>';
}
$html = '
<div class="control-group">
<div class="controls">
<button type="button" style="background-color: red; color: white; padding: 10px 20px; border: none; cursor: pointer;" onclick="testLdapAuthentication();">Test LDAP Authentication</button>
</div>
</div>
<script type="text/javascript">
function testLdapAuthentication() {
Joomla.submitbutton = function(task) {
if (task == "") {
return false;
} else {
var form = document.getElementById("adminForm");
form.task.value = task;
form.submit();
}
}
Joomla.submitbutton("plugin.testLdapAuthentication");
}
</script>';
$body = $doc->getBuffer('component');
$doc->setBuffer($body . $html, 'component');
}
}
}
}