<?php
// jabali-sso-__JABALI_NONCE__.php — auto-generated by jabali-agent for Joomla install __JABALI_INSTALL_ID__.
// Self-deleting single-use admin login. See ADR-0040.
//
// Approach: bootstrap Joomla via its container (the Joomla-5 supported
// path; Factory::getApplication('site') is deprecated and has started
// fatal'ing outside the normal index.php entrypoint), explicitly start
// the session, write the user into session storage, and save. Then
// redirect to /administrator/ — Joomla's admin app shares the same
// session handler so the operator arrives authenticated.

declare(strict_types=1);

// 0. Capture any fatal error that happens below into the webserver log
//    so the operator has something to look at when an SSO attempt 500s.
register_shutdown_function(function (): void {
    $err = error_get_last();
    if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR], true)) {
        error_log('jabali-sso joomla FATAL install __JABALI_INSTALL_ID__: ' . $err['message'] . ' at ' . $err['file'] . ':' . $err['line']);
    }
});

// 1. Reject speculative navigation + non-GET. See ADR-0040.
$secPurpose = $_SERVER['HTTP_SEC_PURPOSE'] ?? '';
$purpose    = $_SERVER['HTTP_PURPOSE']     ?? '';
$xMoz       = $_SERVER['HTTP_X_MOZ']       ?? '';
$xPurpose   = $_SERVER['HTTP_X_PURPOSE']   ?? '';
if ($secPurpose !== ''
 || $purpose === 'prefetch'
 || stripos($xMoz, 'prefetch') !== false
 || $xPurpose === 'preview'
 || ($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'GET') {
    http_response_code(204);
    exit;
}

// 2. Atomic lock claim.
$lock = @fopen(__FILE__, 'r');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
    http_response_code(409);
    exit;
}

// 3. Inline TTL check.
if (filemtime(__FILE__) + __JABALI_TTL_SECONDS__ < time()) {
    http_response_code(410);
    exit;
}

// 4. Bootstrap Joomla as the ADMIN application. Joomla-5 segregates
//    the site session ('session.web.site') from the admin session
//    ('session.web.administrator'); a user written to the site session
//    is NOT authenticated against /administrator/. Since the operator's
//    target is the admin dashboard, bootstrap the admin app here so
//    setting the session below puts the user where they need to be.
//
//    JPATH_BASE points at /administrator (admin app's includes/) but
//    framework.php still loads the site-wide vendor + container config.
//    The session-service aliases below are copied verbatim from
//    administrator/includes/app.php — they're what registers
//    Joomla\Session\SessionInterface against the admin session backend.
//    Without them, $container->get(AdministratorApplication::class)
//    fatals with "Resource SessionInterface has not been registered".
define('_JEXEC', 1);
define('JPATH_BASE', __JABALI_INSTALL_PATH__ . '/administrator');

try {
    require_once JPATH_BASE . '/includes/defines.php';
    require_once JPATH_BASE . '/includes/framework.php';

    $container = \Joomla\CMS\Factory::getContainer();
    $container->alias('session.web', 'session.web.administrator')
        ->alias('session', 'session.web.administrator')
        ->alias('JSession', 'session.web.administrator')
        ->alias(\Joomla\CMS\Session\Session::class, 'session.web.administrator')
        ->alias(\Joomla\Session\Session::class, 'session.web.administrator')
        ->alias(\Joomla\Session\SessionInterface::class, 'session.web.administrator');

    $app = $container->get(\Joomla\CMS\Application\AdministratorApplication::class);
    \Joomla\CMS\Factory::$application = $app;
} catch (\Throwable $e) {
    error_log('jabali-sso joomla bootstrap failed install __JABALI_INSTALL_ID__: ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine());
    http_response_code(500);
    exit;
}

use Joomla\CMS\User\User;
use Joomla\CMS\User\UserHelper;

// 5. Resolve the admin user by username.
$admin_username = __JABALI_ADMIN_USERNAME__;
try {
    $userId = UserHelper::getUserId($admin_username);
} catch (\Throwable $e) {
    error_log('jabali-sso joomla getUserId failed install __JABALI_INSTALL_ID__: ' . $e->getMessage());
    http_response_code(500);
    exit;
}
if (!$userId) {
    error_log('jabali-sso: joomla admin lookup failed for install __JABALI_INSTALL_ID__');
    http_response_code(404);
    exit;
}

try {
    $user = User::getInstance($userId);
} catch (\Throwable $e) {
    error_log('jabali-sso joomla User::getInstance failed install __JABALI_INSTALL_ID__: ' . $e->getMessage());
    http_response_code(500);
    exit;
}
if (!$user || !$user->authorise('core.admin')) {
    error_log('jabali-sso: joomla user lacks core.admin for install __JABALI_INSTALL_ID__');
    http_response_code(403);
    exit;
}

// 6. Establish the session and write the user into it. This is the
//    same write plg_user_joomla's onUserLogin handler does after
//    verifying a password — we skip the password check because the
//    256-bit filename is the capability (ADR-0040).
try {
    $session = $app->getSession();
    if (!$session->isStarted()) {
        $session->start();
    }
    $session->set('user', $user);
    // Bump the session counter so Joomla's auth-check treats this as
    // an active logged-in session rather than a fresh anonymous one.
    $session->set('session.counter', 1);
    $session->set('session.timer.start', time());
    $session->set('session.timer.last', time());
    $session->set('session.timer.now', time());
    // Joomla 5's CMS\Session\Session has no save() method; close()
    // flushes data + releases the lock so /administrator/ on the next
    // request reads the freshly-written user. PHP shutdown would do
    // the same but explicit close means the cookie's Set-Cookie has
    // already been emitted by the time we send Location: below.
    if (method_exists($session, 'close')) {
        $session->close();
    }
} catch (\Throwable $e) {
    error_log('jabali-sso joomla session write failed install __JABALI_INSTALL_ID__: ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine());
    http_response_code(500);
    exit;
}

// 7. Audit + referrer guard.
error_log('jabali-sso: joomla admin login completed for install __JABALI_INSTALL_ID__');
header('Referrer-Policy: no-referrer');

// 8. Unlink LAST.
@unlink(__FILE__);

// 9. Redirect into administrator/. Compute the base path directly from
//    SCRIPT_NAME — Joomla's Uri::base(true) derives from the current
//    request URL and (like Drupal's UrlGenerator) ends up including the
//    SSO PHP filename in the path under some nginx setups, leaving the
//    browser at "/<subdir>/jabali-sso-XXX.php/administrator/" → 404
//    because the SSO file is already unlinked.
$subdir = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? '/'), '/\\');
if ($subdir === '\\' || $subdir === '/') {
    $subdir = '';
}
header('Location: ' . $subdir . '/administrator/');
exit;
