mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-30 15:28:12 +02:00
Added all LDAP changes before removing, because it doesn't work. Will trty to replace it by OIDC
This commit is contained in:
242
roles/web-app-joomla/templates/ldap/auth-trace.php.j2
Normal file
242
roles/web-app-joomla/templates/ldap/auth-trace.php.j2
Normal file
@@ -0,0 +1,242 @@
|
||||
<?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) );
|
Reference in New Issue
Block a user