Sunday, 16 June 2024

Joomla 5 LDAP Plugin

 <?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');

            }

        }

    }

}





ldapauth.xml


<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="authentication" method="upgrade">
    <name>PLG_AUTHENTICATION_LDPAUTH</name>
    <author>Your Name</author>
    <creationDate>2024-06-16</creationDate>
    <version>1.0.0</version>
    <description>LDAP Authentication Plugin</description>
<files>
    <filename plugin="ldapauth">ldapauth.php</filename>
    <folder>fields</folder> <!-- This ensures all files in the fields directory are loaded -->
</files>
    <config>
        <fields name="params">
            <fieldset name="general" label="General Settings">
                <field name="ldaphost1" type="text" label="LDAPHOST 1" description="LDAP Host 1" />
                <field name="ldaphost2" type="text" label="LDAPHOST 2" description="LDAP Host 2" />
                <field name="ldaphost3" type="text" label="LDAPHOST 3" description="LDAP Host 3" />
                <field name="portdefault" type="list" label="Port" description="LDAP Port" default="389">
                    <option value="389">389</option>
                    <option value="636">636</option>
                    <option value="custom">Custom</option>
                </field>
                <field name="customport" type="text" label="Custom Port" description="Custom Port" />
                <field name="ldapv3" type="checkbox" label="LDAPV3" description="Use LDAP V3" />
                <field name="connectionsecurity" type="list" label="Connection Security" description="Connection Security">
                    <option value="none">None</option>
                    <option value="ssl">SSL/TLS</option>
                    <option value="starttls">STARTTLS</option>
                </field>
                <field name="followreferrals" type="checkbox" label="Follow Referrals" description="Follow Referrals" />
                <field name="authorisationmethod" type="list" label="Authorisation Method" description="Authorisation Method">
                    <option value="binddirectly">Bind directly as user</option>
                    <option value="bindsearch">Bind and search</option>
                </field>
                <field name="basedn" type="text" label="BaseDN" description="Base DN" />
                <field name="searchstring" type="text" label="Search String" description="Search String" />
                <field name="usersdn" type="text" label="User's DN" description="User's DN" />
                <field name="connectusername" type="text" label="Connect Username" description="Connect Username" />
                <field name="connectpassword" type="password" label="Connect Password" description="Connect Password" />
                <field name="mapfullname" type="text" label="Map Full Name" description="Map Full Name" />
                <field name="mapemail" type="text" label="Map Email" description="Map Email" />
                <field name="mapuserid" type="text" label="Map User ID" description="Map User ID" />
                <field name="debug" type="checkbox" label="Debug" description="Enable Debugging" />
                <field name="gssapi" type="checkbox" label="GSSAPI" description="Use GSSAPI" />
                <field name="sasl" type="checkbox" label="SASL" description="Use SASL" />
            </fieldset>
<fieldset name="test" label="Test LDAP Settings">
    <!-- Existing fields -->
    <field name="test_username" type="text" label="Test Username" description="Enter a username to test LDAP authentication" />
    <field name="test_password" type="password" label="Test Password" description="Enter a password to test LDAP authentication" />

    <!-- Custom button field for testing -->
    <field name="test_button" type="ldaptestbutton" label="Test Connection" description="Click to test LDAP settings" />

    <!-- Display test results -->
    <field name="test_result" type="textarea" label="Test Result" description="Result of the LDAP test" readonly="true" />
</fieldset>
        </fields>
    </config>
</extension>