mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-29 23:08:06 +02:00
195 lines
7.1 KiB
Django/Jinja
195 lines
7.1 KiB
Django/Jinja
<?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);
|