mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-30 15:28:12 +02:00
Remove non-functional Joomla LDAP integration
- Disabled LDAP feature flag (set to false by default, with comment) - Removed ldapautocreate plugin (PHP + XML) - Deleted LDAP helper tasks (01_ldap_files.yml, 05_ldap.yml, 07_diagnose.yml) - Deleted LDAP CLI helper scripts (cli.php, diagnose.php, plugins.php, auth-trace.php) - Removed LDAP configuration variables from vars/main.yml - Removed LDAP environment variables from env.j2 - Removed LDAP-specific mounts from docker-compose.yml.j2 - Dropped php-ldap installation from Dockerfile - Renamed task files for consistent numbering (02->01_install, 03->02_debug, 04->03_patch, 06->04_assert) Reason: LDAP integration was removed because it was not functional. Conversation: https://chatgpt.com/share/68b09373-7aa8-800f-8f2c-11e27123bad1
This commit is contained in:
@@ -5,7 +5,8 @@ features:
|
|||||||
desktop: true
|
desktop: true
|
||||||
central_database: true
|
central_database: true
|
||||||
logout: true
|
logout: true
|
||||||
ldap: true
|
ldap: false # There is no working free open source LDAP solution 2025-08-28
|
||||||
|
oidc: false # There is no working free open source OIDC solution 2025-08-28
|
||||||
server:
|
server:
|
||||||
domains:
|
domains:
|
||||||
canonical:
|
canonical:
|
||||||
|
@@ -1,158 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* System plugin that auto-creates a Joomla user after successful LDAP authentication.
|
|
||||||
* Now with structured logging (enable via JOOMLA_LDAP_AUTOCREATE_LOG=1).
|
|
||||||
*/
|
|
||||||
|
|
||||||
defined('_JEXEC') || die;
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
use Joomla\CMS\Plugin\CMSPlugin;
|
|
||||||
use Joomla\CMS\User\User;
|
|
||||||
use Joomla\Authentication\Authentication;
|
|
||||||
use Joomla\CMS\Log\Log;
|
|
||||||
|
|
||||||
class PlgSystemLdapautocreate extends CMSPlugin
|
|
||||||
{
|
|
||||||
protected $app;
|
|
||||||
private bool $logEnabled = false;
|
|
||||||
|
|
||||||
public function __construct(&$subject, $config)
|
|
||||||
{
|
|
||||||
parent::__construct($subject, $config);
|
|
||||||
|
|
||||||
// Enable logger only when explicitly requested
|
|
||||||
$this->logEnabled = (bool) filter_var(getenv('JOOMLA_LDAP_AUTOCREATE_LOG') ?: '0', FILTER_VALIDATE_BOOL);
|
|
||||||
|
|
||||||
if ($this->logEnabled) {
|
|
||||||
// Register a dedicated channel and file
|
|
||||||
Log::addLogger(
|
|
||||||
['text_file' => 'ldapauth.log', 'extension' => 'plg_system_ldapautocreate'],
|
|
||||||
Log::ALL,
|
|
||||||
['ldapautocreate']
|
|
||||||
);
|
|
||||||
$this->log('logger-initialized', ['version' => '1.0.0']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function log(string $event, array $ctx = []): void
|
|
||||||
{
|
|
||||||
if (!$this->logEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$payload = json_encode(['event' => $event, 'ctx' => $ctx], JSON_UNESCAPED_SLASHES);
|
|
||||||
Log::add($payload, Log::INFO, 'ldapautocreate');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fires after authentication handlers; frontend and backend.
|
|
||||||
* @param array $options
|
|
||||||
* @param object $response ->status, ->type, ->error_message, ->username, etc.
|
|
||||||
*/
|
|
||||||
public function onUserAfterAuthenticate($options, $response)
|
|
||||||
{
|
|
||||||
// Defensive: normalize shape
|
|
||||||
$status = $response->status ?? null;
|
|
||||||
$type = $response->type ?? '(unknown)';
|
|
||||||
$user = $response->username ?? ($options['username'] ?? null);
|
|
||||||
|
|
||||||
$this->log('after-auth-enter', [
|
|
||||||
'username' => $user,
|
|
||||||
'status' => $status,
|
|
||||||
'type' => $type,
|
|
||||||
'error' => $response->error_message ?? null,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Only proceed when LDAP (or any plugin) actually succeeded
|
|
||||||
if ($status !== Authentication::STATUS_SUCCESS) {
|
|
||||||
$this->log('skip-non-success', ['reason' => 'status!=' . Authentication::STATUS_SUCCESS]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$user) {
|
|
||||||
$this->log('skip-missing-username');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user exists locally, nothing to do
|
|
||||||
$dbo = Factory::getDbo();
|
|
||||||
$count = (int) $dbo->setQuery(
|
|
||||||
$dbo->getQuery(true)
|
|
||||||
->select('COUNT(*)')
|
|
||||||
->from($dbo->quoteName('#__users'))
|
|
||||||
->where($dbo->quoteName('username') . ' = ' . $dbo->quote($user))
|
|
||||||
)->loadResult();
|
|
||||||
|
|
||||||
if ($count > 0) {
|
|
||||||
$this->log('user-exists', ['username' => $user]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read LDAP plugin params (host/port/base_dn/attrs) and fetch cn/mail
|
|
||||||
$ldapExt = $dbo->setQuery(
|
|
||||||
$dbo->getQuery(true)
|
|
||||||
->select('*')
|
|
||||||
->from($dbo->quoteName('#__extensions'))
|
|
||||||
->where("type='plugin' AND folder='authentication' AND element='ldap'")
|
|
||||||
)->loadObject();
|
|
||||||
|
|
||||||
if (!$ldapExt) {
|
|
||||||
$this->log('ldap-plugin-missing');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$p = json_decode($ldapExt->params ?: "{}", true) ?: [];
|
|
||||||
$host = $p['host'] ?? 'openldap';
|
|
||||||
$port = (int) ($p['port'] ?? 389);
|
|
||||||
$baseDn = $p['base_dn'] ?? '';
|
|
||||||
$bindDn = $p['username'] ?? '';
|
|
||||||
$bindPw = $p['password'] ?? '';
|
|
||||||
$attrUid = $p['ldap_uid'] ?? 'uid';
|
|
||||||
$attrMail = $p['ldap_email'] ?? 'mail';
|
|
||||||
$attrName = $p['ldap_fullname'] ?? 'cn';
|
|
||||||
|
|
||||||
$this->log('ldap-params', [
|
|
||||||
'host' => $host, 'port' => $port, 'base_dn' => $baseDn,
|
|
||||||
'attrUid' => $attrUid, 'attrMail' => $attrMail, 'attrName' => $attrName,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$ds = @ldap_connect($host, $port);
|
|
||||||
if (!$ds) { $this->log('ldap-connect-failed'); return; }
|
|
||||||
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
|
|
||||||
@ldap_bind($ds, $bindDn, $bindPw);
|
|
||||||
|
|
||||||
$filter = sprintf('(%s=%s)', $attrUid, ldap_escape($user, '', LDAP_ESCAPE_FILTER));
|
|
||||||
$sr = @ldap_search($ds, $baseDn, $filter, [$attrName, $attrMail]);
|
|
||||||
$entry = $sr ? @ldap_first_entry($ds, $sr) : null;
|
|
||||||
|
|
||||||
$name = $entry ? (@ldap_get_values($ds, $entry, $attrName)[0] ?? $user) : $user;
|
|
||||||
$email = $entry ? (@ldap_get_values($ds, $entry, $attrMail)[0] ?? ($user.'@example.invalid')) : ($user.'@example.invalid');
|
|
||||||
|
|
||||||
if ($ds) { @ldap_unbind($ds); }
|
|
||||||
|
|
||||||
$this->log('creating-user', ['username' => $user, 'name' => $name, 'email' => $email]);
|
|
||||||
|
|
||||||
// Create Joomla user in Registered (id=2)
|
|
||||||
$data = [
|
|
||||||
'name' => $name,
|
|
||||||
'username' => $user,
|
|
||||||
'email' => $email,
|
|
||||||
'password' => bin2hex(random_bytes(12)),
|
|
||||||
'block' => 0,
|
|
||||||
'groups' => [2],
|
|
||||||
];
|
|
||||||
|
|
||||||
$joomUser = new User;
|
|
||||||
if (!$joomUser->bind($data)) {
|
|
||||||
$this->log('user-bind-failed', ['error' => 'bind() returned false']);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$joomUser->save()) {
|
|
||||||
$this->log('user-save-failed', ['error' => 'save() returned false']);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->log('user-created', ['id' => $joomUser->id, 'username' => $user]);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<extension type="plugin" group="system" method="upgrade" version="4.0">
|
|
||||||
<name>plg_system_ldapautocreate</name>
|
|
||||||
<author>Infinito.Nexus</author>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
<description>Auto-create Joomla users after successful LDAP authentication.</description>
|
|
||||||
<files>
|
|
||||||
<filename plugin="ldapautocreate">ldapautocreate.php</filename>
|
|
||||||
</files>
|
|
||||||
</extension>
|
|
@@ -1,46 +0,0 @@
|
|||||||
- name: "Render LDAP CLI helper"
|
|
||||||
template:
|
|
||||||
src: ldap/cli.php.j2
|
|
||||||
dest: "{{ JOOMLA_LDAP_CONF_FILE }}"
|
|
||||||
mode: "0644"
|
|
||||||
when: JOOMLA_LDAP_ENABLED | bool
|
|
||||||
notify: docker compose restart
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: "Ensure ldapautocreate plugin hostdir exists"
|
|
||||||
file:
|
|
||||||
path: "{{ JOOMLA_LDAP_AUT_CRT_HOST_DIR }}"
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: "Deploy ldapautocreate plugin files"
|
|
||||||
copy:
|
|
||||||
src: "ldapautocreate.{{ item }}"
|
|
||||||
dest: "{{ [ JOOMLA_LDAP_AUT_CRT_HOST_DIR, 'ldapautocreate.' ~ item ] | path_join }}"
|
|
||||||
mode: "0644"
|
|
||||||
notify: docker compose restart
|
|
||||||
loop:
|
|
||||||
- php
|
|
||||||
- xml
|
|
||||||
when: JOOMLA_LDAP_AUTO_CREATE_ENABLED | bool
|
|
||||||
|
|
||||||
- name: "Deploy LDAP diagnose CLI"
|
|
||||||
template:
|
|
||||||
src: ldap/diagnose.php.j2
|
|
||||||
dest: "{{ docker_compose.directories.volumes }}/cli-ldap-diagnose.php"
|
|
||||||
mode: "0644"
|
|
||||||
when: MODE_DEBUG | bool
|
|
||||||
|
|
||||||
- name: "Deploy Joomla plugin inspector CLI (list state)"
|
|
||||||
template:
|
|
||||||
src: ldap/plugins.php.j2
|
|
||||||
dest: "{{ docker_compose.directories.volumes }}/cli-plugins.php"
|
|
||||||
mode: "0644"
|
|
||||||
when: MODE_DEBUG | bool
|
|
||||||
|
|
||||||
- name: "Deploy Joomla auth trace CLI"
|
|
||||||
template:
|
|
||||||
src: ldap/auth-trace.php.j2
|
|
||||||
dest: "{{ docker_compose.directories.volumes }}/cli-ldap-auth-trace.php"
|
|
||||||
mode: "0644"
|
|
||||||
when: MODE_DEBUG | bool
|
|
@@ -1,55 +0,0 @@
|
|||||||
- name: "Configure LDAP plugin params via helper"
|
|
||||||
command: >
|
|
||||||
docker exec {{ JOOMLA_CONTAINER }}
|
|
||||||
sh -c 'test -f /var/www/html/cli/cli-ldap.php && php /var/www/html/cli/cli-ldap.php'
|
|
||||||
register: ldap_conf
|
|
||||||
changed_when: "'configured' in ldap_conf.stdout | lower"
|
|
||||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
|
||||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
|
||||||
|
|
||||||
- name: "Register & enable ldapautocreate Joomla system plugin"
|
|
||||||
command: >
|
|
||||||
docker exec {{ JOOMLA_CONTAINER }}
|
|
||||||
sh -lc '
|
|
||||||
test -f /var/www/html/plugins/system/ldapautocreate/ldapautocreate.php ||
|
|
||||||
{ echo "ERROR: plugin file missing"; exit 1; };
|
|
||||||
php -r "
|
|
||||||
define(\"_JEXEC\",1);
|
|
||||||
\$root=\"/var/www/html\";
|
|
||||||
require \$root.\"/includes/defines.php\";
|
|
||||||
require \$root.\"/includes/framework.php\";
|
|
||||||
\$dbo = Joomla\\CMS\\Factory::getDbo();
|
|
||||||
\$ext = \$dbo->setQuery(
|
|
||||||
\"SELECT * FROM #__extensions WHERE type=\\\"plugin\\\" AND folder=\\\"system\\\" AND element=\\\"ldapautocreate\\\"\"
|
|
||||||
)->loadObject();
|
|
||||||
if (!\$ext) {
|
|
||||||
\$row = (object)[
|
|
||||||
\"name\" => \"plg_system_ldapautocreate\",
|
|
||||||
\"type\" => \"plugin\",
|
|
||||||
\"element\" => \"ldapautocreate\",
|
|
||||||
\"folder\" => \"system\",
|
|
||||||
\"enabled\" => 1,
|
|
||||||
\"access\" => 1,
|
|
||||||
\"protected\" => 0,
|
|
||||||
\"manifest_cache\" => \"{}\",
|
|
||||||
\"params\" => \"{}\",
|
|
||||||
\"custom_data\" => \"{}\",
|
|
||||||
\"state\" => 0,
|
|
||||||
\"ordering\" => 0,
|
|
||||||
\"client_id\" => 0
|
|
||||||
];
|
|
||||||
\$dbo->insertObject(\"#__extensions\", \$row);
|
|
||||||
echo \"Plugin registered + enabled\\n\";
|
|
||||||
} else {
|
|
||||||
\$ext->enabled = 1;
|
|
||||||
\$dbo->updateObject(\"#__extensions\", \$ext, \"extension_id\");
|
|
||||||
echo \"Plugin already exists, just enabled\\n\";
|
|
||||||
}
|
|
||||||
"
|
|
||||||
'
|
|
||||||
register: ldapautocreate_reg
|
|
||||||
changed_when: >
|
|
||||||
('registered + enabled' in (ldapautocreate_reg.stdout | lower)) or
|
|
||||||
('just enabled' in (ldapautocreate_reg.stdout | lower))
|
|
||||||
failed_when: ldapautocreate_reg.rc != 0
|
|
||||||
when: JOOMLA_LDAP_AUTO_CREATE_ENABLED | bool
|
|
@@ -1,14 +0,0 @@
|
|||||||
- name: "Run LDAP diagnose"
|
|
||||||
command:
|
|
||||||
argv:
|
|
||||||
- docker
|
|
||||||
- exec
|
|
||||||
- "{{ JOOMLA_CONTAINER }}"
|
|
||||||
- php
|
|
||||||
- /var/www/html/cli/ldap-diagnose.php
|
|
||||||
- "--username={{ users.administrator.username }}"
|
|
||||||
register: ldap_diag
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: ldap_diag.stdout_lines
|
|
@@ -1,40 +1,26 @@
|
|||||||
---
|
---
|
||||||
#- name: "Include role srv-domain-provision for {{ application_id }}"
|
- name: "Include role srv-domain-provision for {{ application_id }}"
|
||||||
# include_role:
|
include_role:
|
||||||
# name: srv-domain-provision
|
name: srv-domain-provision
|
||||||
# loop: "{{ JOOMLA_DOMAINS }}"
|
loop: "{{ JOOMLA_DOMAINS }}"
|
||||||
# loop_control:
|
loop_control:
|
||||||
# loop_var: domain
|
loop_var: domain
|
||||||
# vars:
|
vars:
|
||||||
# http_port: "{{ ports.localhost.http[application_id] }}"
|
http_port: "{{ ports.localhost.http[application_id] }}"
|
||||||
|
|
||||||
- name: "load docker and db for {{ application_id }}"
|
- name: "load docker and db for {{ application_id }}"
|
||||||
include_role:
|
include_role:
|
||||||
name: cmp-db-docker
|
name: cmp-db-docker
|
||||||
vars:
|
vars:
|
||||||
docker_compose_flush_handlers: false
|
docker_compose_flush_handlers: true
|
||||||
|
|
||||||
- name: Include install routines
|
|
||||||
include_tasks: "01_ldap_files.yml"
|
|
||||||
|
|
||||||
- name: "flush docker compose handlers"
|
|
||||||
meta: flush_handlers
|
|
||||||
|
|
||||||
- name: Include install routines
|
- name: Include install routines
|
||||||
include_tasks: "{{ item }}"
|
include_tasks: "{{ item }}"
|
||||||
loop:
|
loop:
|
||||||
- 02_install.yml
|
- 01_install.yml
|
||||||
- 03_debug.yml
|
- 02_debug.yml
|
||||||
- 04_patch.yml
|
- 03_patch.yml
|
||||||
|
|
||||||
- name: Include LDAP routines
|
|
||||||
include_tasks: "05_ldap.yml"
|
|
||||||
when: JOOMLA_LDAP_ENABLED | bool
|
|
||||||
|
|
||||||
- name: Include assert routines
|
- name: Include assert routines
|
||||||
include_tasks: "06_assert.yml"
|
include_tasks: "04_assert.yml"
|
||||||
when: MODE_ASSERT | bool
|
when: MODE_ASSERT | bool
|
||||||
|
|
||||||
- name: Include LDAP diagnose routines
|
|
||||||
include_tasks: "07_diagnose.yml"
|
|
||||||
when: MODE_DEBUG | bool and JOOMLA_LDAP_ENABLED | bool
|
|
@@ -1,15 +1 @@
|
|||||||
FROM {{ JOOMLA_IMAGE }}:{{ JOOMLA_VERSION }}
|
FROM {{ JOOMLA_IMAGE }}:{{ JOOMLA_VERSION }}
|
||||||
{% if JOOMLA_LDAP_ENABLED %}
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
RUN set -eux; \
|
|
||||||
apt-get update; \
|
|
||||||
PHPV="$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')" || PHPV=""; \
|
|
||||||
apt-get install -y --no-install-recommends "php${PHPV}-ldap" \
|
|
||||||
|| ( \
|
|
||||||
apt-get install -y --no-install-recommends libldap2-dev libsasl2-dev pkg-config; \
|
|
||||||
docker-php-ext-configure ldap --with-ldap=/usr --with-ldap-sasl=/usr \
|
|
||||||
|| docker-php-ext-configure ldap --with-ldap=/usr; \
|
|
||||||
docker-php-ext-install -j"$(nproc)" ldap \
|
|
||||||
); \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
{% endif %}
|
|
||||||
|
@@ -9,17 +9,6 @@
|
|||||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||||
volumes:
|
volumes:
|
||||||
- data:/var/www/html
|
- data:/var/www/html
|
||||||
{% if JOOMLA_LDAP_ENABLED %}
|
|
||||||
- {{ JOOMLA_LDAP_CONF_FILE }}:/var/www/html/cli/cli-ldap.php:ro
|
|
||||||
{% if JOOMLA_LDAP_AUTO_CREATE_ENABLED | bool %}
|
|
||||||
- {{ JOOMLA_LDAP_AUT_CRT_HOST_DIR }}:{{ JOOMLA_LDAP_AUT_CRT_DOCK_DIR }}:ro
|
|
||||||
{% endif %}
|
|
||||||
{% if MODE_DEBUG | bool %}
|
|
||||||
- {{ JOOMLA_LDAP_DIAG_HOST_FILE }}:{{ JOOMLA_LDAP_DIAG_DOCK_FILE }}:ro
|
|
||||||
- {{ docker_compose.directories.volumes }}/cli-plugins.php:/var/www/html/cli/cli-plugins.php:ro
|
|
||||||
- {{ docker_compose.directories.volumes }}/cli-ldap-auth-trace.php:/var/www/html/cli/ldap-auth-trace.php:ro
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}"
|
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}"
|
||||||
{% include 'roles/docker-container/templates/healthcheck/curl.yml.j2' %}
|
{% include 'roles/docker-container/templates/healthcheck/curl.yml.j2' %}
|
||||||
|
@@ -12,24 +12,3 @@ JOOMLA_DB_PASSWORD={{ database_password }}
|
|||||||
JOOMLA_DB_NAME={{ database_name }}
|
JOOMLA_DB_NAME={{ database_name }}
|
||||||
JOOMLA_DB_TYPE={{ JOOMLA_DB_CONNECTOR }}
|
JOOMLA_DB_TYPE={{ JOOMLA_DB_CONNECTOR }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if JOOMLA_LDAP_ENABLED %}
|
|
||||||
# LDAP
|
|
||||||
JOOMLA_LDAP_AUTOCREATE_LOG={{ MODE_DEBUG | ternary('1','') }}
|
|
||||||
JOOMLA_LDAP_HOST={{ JOOMLA_LDAP_HOST }}
|
|
||||||
JOOMLA_LDAP_PORT={{ JOOMLA_LDAP_PORT }}
|
|
||||||
JOOMLA_LDAP_BASE_DN={{ JOOMLA_LDAP_BASE_DN }}
|
|
||||||
JOOMLA_LDAP_USER_TREE_DN={{ JOOMLA_LDAP_USER_TREE_DN }}
|
|
||||||
JOOMLA_LDAP_GROUP_TREE_DN={{ JOOMLA_LDAP_GROUP_TREE_DN }}
|
|
||||||
JOOMLA_LDAP_UID_ATTR={{ JOOMLA_LDAP_UID_ATTR }}
|
|
||||||
JOOMLA_LDAP_EMAIL_ATTR={{ JOOMLA_LDAP_EMAIL_ATTR }}
|
|
||||||
JOOMLA_LDAP_NAME_ATTR={{ JOOMLA_LDAP_NAME_ATTR }}
|
|
||||||
JOOMLA_LDAP_BIND_DN={{ JOOMLA_LDAP_BIND_DN }}
|
|
||||||
JOOMLA_LDAP_BIND_PASSWORD={{ JOOMLA_LDAP_BIND_PASSWORD }}
|
|
||||||
JOOMLA_LDAP_USE_STARTTLS={{ JOOMLA_LDAP_USE_STARTTLS | ternary('1','') }}
|
|
||||||
JOOMLA_LDAP_IGNORE_CERT={{ JOOMLA_LDAP_IGNORE_CERT | ternary('1','') }}
|
|
||||||
JOOMLA_LDAP_MAP_FULLNAME={{ JOOMLA_LDAP_MAP_FULLNAME | ternary('1','') }}
|
|
||||||
JOOMLA_LDAP_MAP_EMAIL={{ JOOMLA_LDAP_MAP_EMAIL | ternary('1','') }}
|
|
||||||
JOOMLA_LDAP_AUTH_METHOD={{ JOOMLA_LDAP_AUTH_METHOD }}
|
|
||||||
JOOMLA_LDAP_USER_SEARCH_STRING={{ JOOMLA_LDAP_USER_SEARCH_STRING }}
|
|
||||||
{% endif %}
|
|
@@ -1,242 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* CLI: LDAP auth trace (plugin-accurate, without booting Joomla plugins)
|
|
||||||
*
|
|
||||||
* This script reads Joomla's "Authentication - LDAP" plugin parameters from #__extensions
|
|
||||||
* and then performs the same login flow the plugin would:
|
|
||||||
* - Connect → optional StartTLS → bind (service) → search user → bind as user with given password
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* php /var/www/html/cli/ldap-auth-trace.php --username <u> --password '<p>' [--json]
|
|
||||||
*
|
|
||||||
* Exit codes:
|
|
||||||
* 0 = authentication SUCCESS
|
|
||||||
* 1 = authentication FAILED (see messages)
|
|
||||||
* 2 = usage error
|
|
||||||
* 3 = LDAP plugin row not found / misconfigured
|
|
||||||
* 4 = PHP ldap extension missing
|
|
||||||
*
|
|
||||||
* Notes:
|
|
||||||
* - This bypasses Joomla plugin boot issues in CLI and gives precise LDAP errors.
|
|
||||||
* - Honors LDAP plugin params + ENV overrides (same names as your role uses).
|
|
||||||
*/
|
|
||||||
|
|
||||||
define('_JEXEC', 1);
|
|
||||||
define('JPATH_BASE', __DIR__ . '/..');
|
|
||||||
|
|
||||||
// Bootstrap minimal Joomla DB access (no need to boot Site/CMS app)
|
|
||||||
require JPATH_BASE . '/includes/defines.php';
|
|
||||||
require JPATH_BASE . '/includes/framework.php';
|
|
||||||
|
|
||||||
ini_set('display_errors', '1');
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
|
|
||||||
function getenv_clean(string $key, $default = null) {
|
|
||||||
$v = getenv($key);
|
|
||||||
if ($v === false || $v === '') return $default;
|
|
||||||
return preg_replace('/^(["\'])(.*)\1$/', '$2', $v);
|
|
||||||
}
|
|
||||||
|
|
||||||
function outln($msg) { echo $msg . "\n"; }
|
|
||||||
|
|
||||||
function result($ok, $label, $detail = null) {
|
|
||||||
$prefix = $ok ? '[OK] ' : '[ERR] ';
|
|
||||||
echo $prefix . $label . ($detail !== null ? " — $detail" : '') . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt a full LDAP auth:
|
|
||||||
* - connect (host:port)
|
|
||||||
* - optional StartTLS (negotiate_tls)
|
|
||||||
* - service bind (admin)
|
|
||||||
* - lookup DN (search or users_dn template)
|
|
||||||
* - user bind with provided password
|
|
||||||
*/
|
|
||||||
function ldap_auth_flow(array $cfg, string $username, string $password, bool $asJson): int
|
|
||||||
{
|
|
||||||
$report = [
|
|
||||||
'connect' => null,
|
|
||||||
'starttls' => null,
|
|
||||||
'serviceBind' => null,
|
|
||||||
'userSearch' => null,
|
|
||||||
'userBind' => null,
|
|
||||||
'dn' => null,
|
|
||||||
'messages' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!extension_loaded('ldap')) {
|
|
||||||
$msg = 'PHP LDAP extension not loaded';
|
|
||||||
if ($asJson) echo json_encode(['error' => $msg], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
|
|
||||||
else result(false, $msg);
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect
|
|
||||||
$ds = @ldap_connect($cfg['host'], $cfg['port']);
|
|
||||||
if (!$ds) {
|
|
||||||
$report['connect'] = false;
|
|
||||||
$report['messages'][] = "ldap_connect failed to {$cfg['host']}:{$cfg['port']}";
|
|
||||||
return emit($report, $asJson);
|
|
||||||
}
|
|
||||||
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
|
|
||||||
ldap_set_option($ds, LDAP_OPT_NETWORK_TIMEOUT, 5);
|
|
||||||
$report['connect'] = true;
|
|
||||||
|
|
||||||
// StartTLS if configured
|
|
||||||
if ($cfg['use_tls']) {
|
|
||||||
if (@ldap_start_tls($ds)) {
|
|
||||||
$report['starttls'] = true;
|
|
||||||
} else {
|
|
||||||
$report['starttls'] = false;
|
|
||||||
$report['messages'][] = 'StartTLS failed: ' . ldap_error($ds);
|
|
||||||
return emit($report, $asJson, $ds);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$report['starttls'] = null; // not requested
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service/admin bind (may be empty for anonymous)
|
|
||||||
$serviceBound = @ldap_bind($ds, $cfg['bind_dn'], $cfg['bind_pw']);
|
|
||||||
if (!$serviceBound) {
|
|
||||||
$report['serviceBind'] = false;
|
|
||||||
$report['messages'][] = 'Service bind failed: ' . ldap_error($ds) . sprintf(" (bind_dn=%s)", $cfg['bind_dn'] ?: '(anonymous)');
|
|
||||||
return emit($report, $asJson, $ds);
|
|
||||||
}
|
|
||||||
$report['serviceBind'] = true;
|
|
||||||
|
|
||||||
// Resolve user DN
|
|
||||||
$userDn = null;
|
|
||||||
|
|
||||||
if (strtolower($cfg['auth_method']) === 'bind') {
|
|
||||||
// Direct template substitution (no search)
|
|
||||||
$userDn = str_replace(['[username]', '[USER]', '[uid]'], $username, $cfg['users_dn']);
|
|
||||||
$report['userSearch'] = 'skipped (bind mode)';
|
|
||||||
} else {
|
|
||||||
// search mode: find DN first
|
|
||||||
$filter = sprintf('(%s=%s)', $cfg['uid_attr'], ldap_escape($username, '', LDAP_ESCAPE_FILTER));
|
|
||||||
$sr = @ldap_search($ds, $cfg['base_dn'], $filter, [$cfg['name_attr'], $cfg['mail_attr']]);
|
|
||||||
if (!$sr) {
|
|
||||||
$report['userSearch'] = false;
|
|
||||||
$report['messages'][] = 'Search failed: ' . ldap_error($ds) . " (base_dn={$cfg['base_dn']}, filter={$filter})";
|
|
||||||
return emit($report, $asJson, $ds);
|
|
||||||
}
|
|
||||||
$entries = @ldap_get_entries($ds, $sr);
|
|
||||||
$count = (int)($entries['count'] ?? 0);
|
|
||||||
if ($count < 1) {
|
|
||||||
$report['userSearch'] = false;
|
|
||||||
$report['messages'][] = "User not found under base_dn={$cfg['base_dn']} with {$cfg['uid_attr']}={$username}";
|
|
||||||
return emit($report, $asJson, $ds);
|
|
||||||
}
|
|
||||||
$userDn = $entries[0]['dn'] ?? null;
|
|
||||||
$report['userSearch'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$userDn) {
|
|
||||||
$report['messages'][] = 'No user DN resolved.';
|
|
||||||
return emit($report, $asJson, $ds);
|
|
||||||
}
|
|
||||||
$report['dn'] = $userDn;
|
|
||||||
|
|
||||||
// Attempt user bind with provided password
|
|
||||||
$ok = @ldap_bind($ds, $userDn, $password);
|
|
||||||
if ($ok) {
|
|
||||||
$report['userBind'] = true;
|
|
||||||
return emit($report, $asJson, $ds, /*success*/true);
|
|
||||||
} else {
|
|
||||||
$report['userBind'] = false;
|
|
||||||
$report['messages'][] = 'User bind failed: ' . ldap_error($ds);
|
|
||||||
return emit($report, $asJson, $ds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function emit(array $report, bool $asJson, $ds = null, bool $success = false): int
|
|
||||||
{
|
|
||||||
if ($asJson) {
|
|
||||||
echo json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
|
|
||||||
} else {
|
|
||||||
if ($report['connect'] !== null) result((bool)$report['connect'], 'Connected');
|
|
||||||
if ($report['starttls'] !== null) result((bool)$report['starttls'], 'StartTLS');
|
|
||||||
if ($report['serviceBind'] !== null) result((bool)$report['serviceBind'], 'Service bind');
|
|
||||||
if ($report['userSearch'] !== null && $report['userSearch'] !== 'skipped (bind mode)') {
|
|
||||||
result((bool)$report['userSearch'], 'User search');
|
|
||||||
} elseif ($report['userSearch'] === 'skipped (bind mode)') {
|
|
||||||
outln('Info User search: skipped (bind mode)');
|
|
||||||
}
|
|
||||||
if ($report['dn']) outln('Info User DN: ' . $report['dn']);
|
|
||||||
if ($report['userBind'] !== null) result((bool)$report['userBind'], 'User bind');
|
|
||||||
|
|
||||||
foreach ($report['messages'] as $m) {
|
|
||||||
outln('Note ' . $m);
|
|
||||||
}
|
|
||||||
|
|
||||||
outln($success ? 'Overall: SUCCESS' : 'Overall: FAIL');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($ds) { @ldap_unbind($ds); }
|
|
||||||
return $success ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------ Parse CLI args ------
|
|
||||||
$args = getopt('', ['username:', 'password:', 'json']);
|
|
||||||
$username = $args['username'] ?? null;
|
|
||||||
$password = $args['password'] ?? null;
|
|
||||||
$asJson = array_key_exists('json', $args);
|
|
||||||
|
|
||||||
if (!$username || $password === null) {
|
|
||||||
fwrite(STDERR, "Usage: --username <u> --password '<p>' [--json]\n");
|
|
||||||
exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------ Load LDAP plugin params from DB ------
|
|
||||||
$dbo = Factory::getDbo();
|
|
||||||
$q = $dbo->getQuery(true)
|
|
||||||
->select('*')
|
|
||||||
->from($dbo->quoteName('#__extensions'))
|
|
||||||
->where($dbo->quoteName('type') . ' = ' . $dbo->quote('plugin'))
|
|
||||||
->where($dbo->quoteName('folder') . ' = ' . $dbo->quote('authentication'))
|
|
||||||
->where($dbo->quoteName('element') . ' = ' . $dbo->quote('ldap'));
|
|
||||||
$dbo->setQuery($q);
|
|
||||||
$ext = $dbo->loadObject();
|
|
||||||
|
|
||||||
if (!$ext) {
|
|
||||||
if ($asJson) echo json_encode(['error' => 'LDAP plugin row not found in #__extensions'], JSON_PRETTY_PRINT) . "\n";
|
|
||||||
else result(false, 'LDAP plugin not found in #__extensions (authentication/ldap)');
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
$params = json_decode($ext->params ?: "{}", true) ?: [];
|
|
||||||
|
|
||||||
// Effective config with ENV overrides (matches your role variables)
|
|
||||||
$cfg = [
|
|
||||||
'host' => getenv_clean('JOOMLA_LDAP_HOST', $params['host'] ?? 'openldap'),
|
|
||||||
'port' => (int) getenv_clean('JOOMLA_LDAP_PORT', (string)($params['port'] ?? 389)),
|
|
||||||
'base_dn' => getenv_clean('JOOMLA_LDAP_BASE_DN', $params['base_dn'] ?? ''),
|
|
||||||
'users_dn' => getenv_clean('JOOMLA_LDAP_USER_TREE_DN', $params['users_dn'] ?? ''),
|
|
||||||
'bind_dn' => getenv_clean('JOOMLA_LDAP_BIND_DN', $params['username'] ?? ''),
|
|
||||||
'bind_pw' => getenv_clean('JOOMLA_LDAP_BIND_PASSWORD', $params['password'] ?? ''),
|
|
||||||
'use_tls' => filter_var(getenv_clean('JOOMLA_LDAP_USE_STARTTLS', $params['negotiate_tls'] ?? false), FILTER_VALIDATE_BOOL),
|
|
||||||
'auth_method' => getenv_clean('JOOMLA_LDAP_AUTH_METHOD', $params['auth_method'] ?? 'search'),
|
|
||||||
'search_string' => getenv_clean('JOOMLA_LDAP_USER_SEARCH_STRING', $params['search_string'] ?? 'uid=[username]'),
|
|
||||||
'uid_attr' => getenv_clean('JOOMLA_LDAP_UID_ATTR', $params['ldap_uid'] ?? 'uid'),
|
|
||||||
'mail_attr' => getenv_clean('JOOMLA_LDAP_EMAIL_ATTR', $params['ldap_email'] ?? 'mail'),
|
|
||||||
'name_attr' => getenv_clean('JOOMLA_LDAP_NAME_ATTR', $params['ldap_fullname'] ?? 'cn'),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Print effective config (non-JSON mode) with masked password
|
|
||||||
if (!$asJson) {
|
|
||||||
outln('Effective LDAP configuration:');
|
|
||||||
outln(' host: ' . $cfg['host']);
|
|
||||||
outln(' port: ' . $cfg['port']);
|
|
||||||
outln(' base_dn: ' . $cfg['base_dn']);
|
|
||||||
outln(' users_dn: ' . $cfg['users_dn']);
|
|
||||||
outln(' bind_dn: ' . ($cfg['bind_dn'] ?: '(anonymous)'));
|
|
||||||
outln(' bind_pw: ' . ($cfg['bind_pw'] !== '' ? '***' : ''));
|
|
||||||
outln(' use_tls: ' . ($cfg['use_tls'] ? '1' : ''));
|
|
||||||
outln(' auth_method: ' . $cfg['auth_method']);
|
|
||||||
outln(' uid_attr: ' . $cfg['uid_attr']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the flow
|
|
||||||
exit( ldap_auth_flow($cfg, $username, $password, $asJson) );
|
|
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Joomla CLI script to enable and configure the Authentication - LDAP plugin.
|
|
||||||
// Safe to run multiple times. Uses only Factory::getDbo() (no web/administrator app context required).
|
|
||||||
|
|
||||||
define('_JEXEC', 1);
|
|
||||||
define('JPATH_BASE', __DIR__ . '/..');
|
|
||||||
|
|
||||||
// Load Joomla framework
|
|
||||||
require JPATH_BASE . '/includes/defines.php';
|
|
||||||
require JPATH_BASE . '/includes/framework.php';
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
|
|
||||||
// Database driver from Factory
|
|
||||||
$dbo = Factory::getDbo();
|
|
||||||
|
|
||||||
// Locate the LDAP plugin row in #__extensions
|
|
||||||
$query = $dbo->getQuery(true)
|
|
||||||
->select('*')
|
|
||||||
->from($dbo->quoteName('#__extensions'))
|
|
||||||
->where($dbo->quoteName('type') . ' = ' . $dbo->quote('plugin'))
|
|
||||||
->where($dbo->quoteName('folder') . ' = ' . $dbo->quote('authentication'))
|
|
||||||
->where($dbo->quoteName('element') . ' = ' . $dbo->quote('ldap'));
|
|
||||||
$dbo->setQuery($query);
|
|
||||||
$ext = $dbo->loadObject();
|
|
||||||
|
|
||||||
if (!$ext) {
|
|
||||||
fwrite(STDERR, "LDAP plugin not found.\n");
|
|
||||||
exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to strip quotes if present in env-file values
|
|
||||||
$get = static fn($k) => preg_replace('/^(["\'])(.*)\1$/', '$2', getenv($k) ?: '');
|
|
||||||
|
|
||||||
// Desired plugin parameters (must match Joomla LDAP plugin schema)
|
|
||||||
$desired = [
|
|
||||||
// Connection settings
|
|
||||||
"host" => $get('JOOMLA_LDAP_HOST'),
|
|
||||||
"port" => (int) $get('JOOMLA_LDAP_PORT'),
|
|
||||||
"use_ldapV3" => true,
|
|
||||||
"negotiate_tls" => (bool) $get('JOOMLA_LDAP_USE_STARTTLS'),
|
|
||||||
"no_referrals" => false,
|
|
||||||
|
|
||||||
// Authentication settings
|
|
||||||
"auth_method" => $get('JOOMLA_LDAP_AUTH_METHOD') ?: "search", // "search" or "bind"
|
|
||||||
"base_dn" => $get('JOOMLA_LDAP_BASE_DN'),
|
|
||||||
"search_string" => $get('JOOMLA_LDAP_USER_SEARCH_STRING'), // e.g. uid=[username]
|
|
||||||
"users_dn" => $get('JOOMLA_LDAP_USER_TREE_DN'), // required for "bind" mode
|
|
||||||
"username" => $get('JOOMLA_LDAP_BIND_DN'),
|
|
||||||
"password" => $get('JOOMLA_LDAP_BIND_PASSWORD'),
|
|
||||||
|
|
||||||
// Attribute mapping
|
|
||||||
"ldap_uid" => $get('JOOMLA_LDAP_UID_ATTR') ?: "uid",
|
|
||||||
"ldap_email" => $get('JOOMLA_LDAP_EMAIL_ATTR') ?: "mail",
|
|
||||||
"ldap_fullname" => $get('JOOMLA_LDAP_NAME_ATTR') ?: "cn",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Merge current parameters with desired values
|
|
||||||
$current = json_decode($ext->params ?: "{}", true) ?: [];
|
|
||||||
$clean = array_filter($desired, static fn($v) => $v !== null && $v !== '');
|
|
||||||
$merged = array_replace($current, $clean);
|
|
||||||
|
|
||||||
// Save back to database and enable the plugin
|
|
||||||
$ext->params = json_encode($merged, JSON_UNESCAPED_SLASHES);
|
|
||||||
$ext->enabled = 1;
|
|
||||||
$dbo->updateObject('#__extensions', $ext, 'extension_id');
|
|
||||||
|
|
||||||
echo "LDAP plugin enabled={$ext->enabled} and configured.\n";
|
|
@@ -1,194 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Joomla LDAP Diagnostic CLI
|
|
||||||
*
|
|
||||||
* Usage inside the container:
|
|
||||||
* php /var/www/html/cli/ldap-diagnose.php --username <uid> [--verbose]
|
|
||||||
*
|
|
||||||
* Behavior:
|
|
||||||
* - Loads the "Authentication - LDAP" plugin parameters from #__extensions.
|
|
||||||
* - Allows ENV overrides for any LDAP setting (see list below).
|
|
||||||
* - Checks step-by-step: PHP-LDAP → connect → (optional) StartTLS → bind → search → read attributes.
|
|
||||||
* - Optionally previews the candidate DN for user-bind when auth_method=bind (no password tested).
|
|
||||||
*
|
|
||||||
* Supported ENV overrides (optional):
|
|
||||||
* JOOMLA_LDAP_HOST
|
|
||||||
* JOOMLA_LDAP_PORT
|
|
||||||
* JOOMLA_LDAP_BASE_DN
|
|
||||||
* JOOMLA_LDAP_USER_TREE_DN
|
|
||||||
* JOOMLA_LDAP_BIND_DN
|
|
||||||
* JOOMLA_LDAP_BIND_PASSWORD
|
|
||||||
* JOOMLA_LDAP_USE_STARTTLS (true/false)
|
|
||||||
* JOOMLA_LDAP_AUTH_METHOD ("search" or "bind")
|
|
||||||
* JOOMLA_LDAP_USER_SEARCH_STRING (e.g., "uid=[username]")
|
|
||||||
* JOOMLA_LDAP_UID_ATTR (e.g., "uid")
|
|
||||||
* JOOMLA_LDAP_EMAIL_ATTR (e.g., "mail")
|
|
||||||
* JOOMLA_LDAP_NAME_ATTR (e.g., "cn")
|
|
||||||
* TEST_USERNAME (fallback if --username is not passed)
|
|
||||||
*/
|
|
||||||
|
|
||||||
define('_JEXEC', 1);
|
|
||||||
define('JPATH_BASE', __DIR__ . '/..');
|
|
||||||
|
|
||||||
require JPATH_BASE . '/includes/defines.php';
|
|
||||||
require JPATH_BASE . '/includes/framework.php';
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
|
|
||||||
function println($msg, ?bool $ok = null, int $indent = 0): void {
|
|
||||||
$pad = str_repeat(' ', $indent);
|
|
||||||
if ($ok === true) {
|
|
||||||
echo $pad . "[OK] $msg\n";
|
|
||||||
} elseif ($ok === false) {
|
|
||||||
echo $pad . "[ERR] $msg\n";
|
|
||||||
} else {
|
|
||||||
echo $pad . "$msg\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getenv_clean(string $key, $default = null) {
|
|
||||||
$v = getenv($key);
|
|
||||||
if ($v === false || $v === '') return $default;
|
|
||||||
// Strip surrounding single/double quotes if present
|
|
||||||
return preg_replace('/^(["\'])(.*)\1$/', '$2', $v);
|
|
||||||
}
|
|
||||||
|
|
||||||
function env_bool($key, $default = false): bool {
|
|
||||||
$v = getenv_clean($key, null);
|
|
||||||
if ($v === null) return (bool)$default;
|
|
||||||
return filter_var($v, FILTER_VALIDATE_BOOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- CLI args
|
|
||||||
$args = getopt('', ['username:', 'verbose']);
|
|
||||||
$username = $args['username'] ?? getenv_clean('TEST_USERNAME', null);
|
|
||||||
$verbose = array_key_exists('verbose', $args);
|
|
||||||
|
|
||||||
// ---- Preflight: php-ldap
|
|
||||||
if (!extension_loaded('ldap')) {
|
|
||||||
println('PHP LDAP extension not loaded (php-ldap missing in the image).', false);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
println('PHP LDAP extension: loaded', true);
|
|
||||||
|
|
||||||
// ---- Load LDAP plugin params from DB
|
|
||||||
$dbo = Factory::getDbo();
|
|
||||||
$q = $dbo->getQuery(true)
|
|
||||||
->select('*')
|
|
||||||
->from($dbo->quoteName('#__extensions'))
|
|
||||||
->where($dbo->quoteName('type') . ' = ' . $dbo->quote('plugin'))
|
|
||||||
->where($dbo->quoteName('folder') . ' = ' . $dbo->quote('authentication'))
|
|
||||||
->where($dbo->quoteName('element') . ' = ' . $dbo->quote('ldap'));
|
|
||||||
$dbo->setQuery($q);
|
|
||||||
$ext = $dbo->loadObject();
|
|
||||||
if (!$ext) {
|
|
||||||
println('LDAP plugin row not found in #__extensions (authentication/ldap).', false);
|
|
||||||
exit(2);
|
|
||||||
}
|
|
||||||
$params = json_decode($ext->params ?: "{}", true) ?: [];
|
|
||||||
|
|
||||||
// ---- Effective config (DB params with ENV overrides)
|
|
||||||
$cfg = [
|
|
||||||
'host' => getenv_clean('JOOMLA_LDAP_HOST', $params['host'] ?? 'openldap'),
|
|
||||||
'port' => (int) getenv_clean('JOOMLA_LDAP_PORT', (string)($params['port'] ?? 389)),
|
|
||||||
'base_dn' => getenv_clean('JOOMLA_LDAP_BASE_DN', $params['base_dn'] ?? ''),
|
|
||||||
'users_dn' => getenv_clean('JOOMLA_LDAP_USER_TREE_DN', $params['users_dn'] ?? ''),
|
|
||||||
'bind_dn' => getenv_clean('JOOMLA_LDAP_BIND_DN', $params['username'] ?? ''),
|
|
||||||
'bind_pw' => getenv_clean('JOOMLA_LDAP_BIND_PASSWORD', $params['password'] ?? ''),
|
|
||||||
'use_tls' => env_bool('JOOMLA_LDAP_USE_STARTTLS', $params['negotiate_tls'] ?? false),
|
|
||||||
'auth_method' => getenv_clean('JOOMLA_LDAP_AUTH_METHOD', $params['auth_method'] ?? 'search'),
|
|
||||||
'search_string' => getenv_clean('JOOMLA_LDAP_USER_SEARCH_STRING', $params['search_string'] ?? 'uid=[username]'),
|
|
||||||
'uid_attr' => getenv_clean('JOOMLA_LDAP_UID_ATTR', $params['ldap_uid'] ?? 'uid'),
|
|
||||||
'mail_attr' => getenv_clean('JOOMLA_LDAP_EMAIL_ATTR', $params['ldap_email'] ?? 'mail'),
|
|
||||||
'name_attr' => getenv_clean('JOOMLA_LDAP_NAME_ATTR', $params['ldap_fullname'] ?? 'cn'),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!$username) {
|
|
||||||
println('Missing username. Provide --username <uid> or set TEST_USERNAME.', false);
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Print effective config (mask password)
|
|
||||||
$cfg_to_show = $cfg;
|
|
||||||
$cfg_to_show['bind_pw'] = ($cfg['bind_pw'] !== '') ? '***' : '';
|
|
||||||
println('Effective LDAP configuration:');
|
|
||||||
foreach ($cfg_to_show as $k => $v) {
|
|
||||||
println("$k: $v", null, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Connect
|
|
||||||
$ds = @ldap_connect($cfg['host'], $cfg['port']);
|
|
||||||
if (!$ds) {
|
|
||||||
println("ldap_connect to {$cfg['host']}:{$cfg['port']} failed.", false);
|
|
||||||
exit(4);
|
|
||||||
}
|
|
||||||
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
|
|
||||||
ldap_set_option($ds, LDAP_OPT_NETWORK_TIMEOUT, 5);
|
|
||||||
println("ldap_connect to {$cfg['host']}:{$cfg['port']}", true);
|
|
||||||
|
|
||||||
// ---- Optional StartTLS
|
|
||||||
if ($cfg['use_tls']) {
|
|
||||||
if (@ldap_start_tls($ds)) {
|
|
||||||
println('StartTLS negotiated', true);
|
|
||||||
} else {
|
|
||||||
println('StartTLS failed: ' . ldap_error($ds), false);
|
|
||||||
exit(5);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println('StartTLS: disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Service/Admin bind
|
|
||||||
if ($cfg['bind_dn'] !== '') {
|
|
||||||
if (@ldap_bind($ds, $cfg['bind_dn'], $cfg['bind_pw'])) {
|
|
||||||
println("Bind as {$cfg['bind_dn']}", true);
|
|
||||||
} else {
|
|
||||||
println("Bind failed for {$cfg['bind_dn']}: " . ldap_error($ds), false);
|
|
||||||
exit(6);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Anonymous bind
|
|
||||||
if (@ldap_bind($ds)) {
|
|
||||||
println('Anonymous bind', true);
|
|
||||||
} else {
|
|
||||||
println('Anonymous bind failed: ' . ldap_error($ds), false);
|
|
||||||
exit(6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Search user entry
|
|
||||||
$filter = sprintf('(%s=%s)', $cfg['uid_attr'], ldap_escape($username, '', LDAP_ESCAPE_FILTER));
|
|
||||||
$attrs = [$cfg['uid_attr'], $cfg['mail_attr'], $cfg['name_attr']];
|
|
||||||
$sr = @ldap_search($ds, $cfg['base_dn'], $filter, $attrs);
|
|
||||||
if (!$sr) {
|
|
||||||
println("Search failed under base_dn={$cfg['base_dn']} filter={$filter}: " . ldap_error($ds), false);
|
|
||||||
exit(7);
|
|
||||||
}
|
|
||||||
$entries = @ldap_get_entries($ds, $sr);
|
|
||||||
$count = (int)($entries['count'] ?? 0);
|
|
||||||
println("Search entries returned: $count", $count > 0);
|
|
||||||
|
|
||||||
if ($count < 1) {
|
|
||||||
println('No entry found. Check base_dn, uid_attr, filter, and where the user actually lives.', false, 1);
|
|
||||||
@ldap_unbind($ds);
|
|
||||||
exit(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Print first entry summary
|
|
||||||
$dn = $entries[0]['dn'] ?? '(unknown DN)';
|
|
||||||
$name = $entries[0][strtolower($cfg['name_attr'])][0] ?? '(missing name attribute)';
|
|
||||||
$mail = $entries[0][strtolower($cfg['mail_attr'])][0] ?? '(missing mail attribute)';
|
|
||||||
println("Found DN: $dn", true, 1);
|
|
||||||
println("Name: $name", null, 1);
|
|
||||||
println("Mail: $mail", null, 1);
|
|
||||||
|
|
||||||
// ---- If auth_method=bind, preview candidate DN (no password test here)
|
|
||||||
if (strtolower($cfg['auth_method']) === 'bind') {
|
|
||||||
$candidate = str_replace(['[username]', '[USER]', '[uid]'], $username, $cfg['users_dn']);
|
|
||||||
println("auth_method=bind → candidate DN for user-bind: $candidate");
|
|
||||||
println("Note: This script does not attempt the real user password bind.", null, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ldap_unbind($ds);
|
|
||||||
println('Diagnosis finished: basic LDAP connectivity and search are OK.', true);
|
|
||||||
exit(0);
|
|
@@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Joomla CLI: List plugin states from #__extensions
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* php /var/www/html/cli/cli-plugins.php [filter]
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* php cli-plugins.php ldap
|
|
||||||
* php cli-plugins.php authentication
|
|
||||||
*/
|
|
||||||
|
|
||||||
define('_JEXEC', 1);
|
|
||||||
define('JPATH_BASE', __DIR__ . '/..');
|
|
||||||
|
|
||||||
require JPATH_BASE . '/includes/defines.php';
|
|
||||||
require JPATH_BASE . '/includes/framework.php';
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
|
|
||||||
$dbo = Factory::getDbo();
|
|
||||||
|
|
||||||
// Optional filter argument
|
|
||||||
$filter = $argv[1] ?? null;
|
|
||||||
|
|
||||||
// Build query
|
|
||||||
$query = $dbo->getQuery(true)
|
|
||||||
->select('*')
|
|
||||||
->from($dbo->quoteName('#__extensions'))
|
|
||||||
->where($dbo->quoteName('type') . ' = ' . $dbo->quote('plugin'));
|
|
||||||
|
|
||||||
if ($filter) {
|
|
||||||
$query->where(
|
|
||||||
'(' .
|
|
||||||
$dbo->quoteName('element') . ' LIKE ' . $dbo->quote("%$filter%") . ' OR ' .
|
|
||||||
$dbo->quoteName('folder') . ' LIKE ' . $dbo->quote("%$filter%") .
|
|
||||||
')'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$dbo->setQuery($query);
|
|
||||||
$rows = $dbo->loadObjectList();
|
|
||||||
|
|
||||||
if (!$rows) {
|
|
||||||
echo "No plugins found.\n";
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
printf(
|
|
||||||
"[%s/%s] enabled=%d ordering=%d access=%d\n params=%s\n\n",
|
|
||||||
$row->folder,
|
|
||||||
$row->element,
|
|
||||||
$row->enabled,
|
|
||||||
$row->ordering,
|
|
||||||
$row->access,
|
|
||||||
$row->params ?: '{}'
|
|
||||||
);
|
|
||||||
}
|
|
@@ -19,30 +19,3 @@ JOOMLA_USER_NAME: "{{ users.administrator.username }}"
|
|||||||
JOOMLA_USER: "{{ JOOMLA_USER_NAME | capitalize }}"
|
JOOMLA_USER: "{{ JOOMLA_USER_NAME | capitalize }}"
|
||||||
JOOMLA_USER_PASSWORD: "{{ users.administrator.password }}"
|
JOOMLA_USER_PASSWORD: "{{ users.administrator.password }}"
|
||||||
JOOMLA_USER_EMAIL: "{{ users.administrator.email }}"
|
JOOMLA_USER_EMAIL: "{{ users.administrator.email }}"
|
||||||
|
|
||||||
# LDAP
|
|
||||||
JOOMLA_LDAP_CONF_FILE: "{{ [ docker_compose.directories.volumes, 'cli-ldap.php' ] | path_join }}"
|
|
||||||
JOOMLA_LDAP_ENABLED: "{{ applications | get_app_conf(application_id, 'features.ldap') }}"
|
|
||||||
JOOMLA_LDAP_AUTO_CREATE_ENABLED: "{{ applications | get_app_conf(application_id, 'autocreate_users') }}"
|
|
||||||
JOOMLA_LDAP_HOST: "{{ LDAP.SERVER.DOMAIN }}"
|
|
||||||
JOOMLA_LDAP_PORT: "{{ LDAP.SERVER.PORT }}"
|
|
||||||
JOOMLA_LDAP_BASE_DN: "{{ LDAP.DN.ROOT }}"
|
|
||||||
JOOMLA_LDAP_USER_TREE_DN: "{{ LDAP.DN.OU.USERS }}"
|
|
||||||
JOOMLA_LDAP_GROUP_TREE_DN: "{{ LDAP.DN.OU.GROUPS }}"
|
|
||||||
JOOMLA_LDAP_UID_ATTR: "{{ LDAP.USER.ATTRIBUTES.ID }}" # e.g. uid
|
|
||||||
JOOMLA_LDAP_EMAIL_ATTR: "{{ LDAP.USER.ATTRIBUTES.MAIL }}"
|
|
||||||
JOOMLA_LDAP_NAME_ATTR: "{{ LDAP.USER.ATTRIBUTES.FULLNAME }}"
|
|
||||||
JOOMLA_LDAP_BIND_DN: "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
|
|
||||||
JOOMLA_LDAP_BIND_PASSWORD: "{{ LDAP.BIND_CREDENTIAL }}"
|
|
||||||
JOOMLA_LDAP_USE_STARTTLS: false
|
|
||||||
JOOMLA_LDAP_IGNORE_CERT: true
|
|
||||||
JOOMLA_LDAP_MAP_FULLNAME: true
|
|
||||||
JOOMLA_LDAP_MAP_EMAIL: true
|
|
||||||
JOOMLA_LDAP_AUTH_METHOD: "search" # "bind" or "search"
|
|
||||||
JOOMLA_LDAP_USER_SEARCH_STRING: "{{ JOOMLA_LDAP_UID_ATTR }}=[username]"
|
|
||||||
JOOMLA_LDAP_AUT_CRT_HOST_DIR: "{{ [ docker_compose.directories.volumes, 'ldapautocreate' ] | path_join }}"
|
|
||||||
JOOMLA_LDAP_AUT_CRT_DOCK_DIR: "/var/www/html/plugins/system/ldapautocreate"
|
|
||||||
|
|
||||||
# Diagnose
|
|
||||||
JOOMLA_LDAP_DIAG_HOST_FILE: "{{ [ docker_compose.directories.volumes, 'cli-ldap-diagnose.php' ] | path_join }}"
|
|
||||||
JOOMLA_LDAP_DIAG_DOCK_FILE: "/var/www/html/cli/ldap-diagnose.php"
|
|
||||||
|
Reference in New Issue
Block a user