*/ function dsp_jupyter_defaults(): array { return [ 'base_url' => 'https://localhost', 'token' => 'dsp-token', 'workspace_root' => 'datasources', 'port' => '443', ]; } /** * Determines the external base URL for the Jupyter service. * * @return string */ function dsp_jupyter_base_url(): string { $configured = getenv('JUPYTER_EXTERNAL_URL'); if ($configured) { return rtrim($configured, '/'); } $defaults = dsp_jupyter_defaults(); $scheme = (!empty($_SERVER['HTTPS']) && strtolower((string) $_SERVER['HTTPS']) !== 'off') ? 'https' : 'http'; $hostHeader = $_SERVER['HTTP_HOST'] ?? ''; if ($hostHeader) { $hostname = explode(':', $hostHeader)[0]; $port = dsp_jupyter_port(); $authority = $hostname; $portIsDefault = ($scheme === 'http' && $port === '80') || ($scheme === 'https' && $port === '443'); if (!$portIsDefault) { $authority .= ':' . $port; } return sprintf('%s://%s', $scheme, $authority); } return rtrim($defaults['base_url'], '/'); } /** * Retrieves the token used to authenticate with Jupyter. * * @return string */ function dsp_jupyter_token(): string { $authStrategy = strtolower((string) getenv('JUPYTERHUB_AUTH_STRATEGY')); if ($authStrategy === 'oauth') { return ''; } $defaults = dsp_jupyter_defaults(); $envToken = getenv('JUPYTER_TOKEN'); if ($envToken !== false) { return $envToken; } return $defaults['token']; } /** * Resolves the username JupyterHub should use for the active user. * * The template can be customised via JUPYTERHUB_USERNAME_TEMPLATE. * Supported placeholders: * {person_id} - numeric person identifier from the session * {username} - DSP username from the session * {email} - Session email if available * * @param int|null $personId Optional explicit person ID * @return string|null Sanitised username or null when it cannot be determined. */ function dsp_resolve_jupyterhub_username(?int $personId, ?string $username = null, ?string $email = null): ?string { if ($personId === null || $personId <= 0) { return null; } $template = getenv('JUPYTERHUB_USERNAME_TEMPLATE') ?: 'user_{person_id}'; $usernameRaw = str_replace( ['{person_id}', '{username}', '{email}'], [ (string) $personId, (string) $username, (string) $email, ], $template ); $usernameSanitised = preg_replace('/[^a-zA-Z0-9._-]+/', '-', $usernameRaw); $usernameSanitised = trim((string) $usernameSanitised, "-_."); return $usernameSanitised !== '' ? $usernameSanitised : null; } function dsp_jupyterhub_username(?int $personId = null): ?string { if ($personId === null) { $personId = isset($_SESSION['person_id']) ? (int) $_SESSION['person_id'] : null; } $username = $_SESSION['username'] ?? null; $email = $_SESSION['email'] ?? null; return dsp_resolve_jupyterhub_username($personId, $username, $email); } /** * Builds the per-user route served by JupyterHub. * * The path template can be overridden with JUPYTERHUB_USER_PATH (e.g. "user/{username}/lab"). * * @param string $baseUrl Hub base URL. * @param int|null $personId Optional explicit person ID. * @return string Absolute path including the user segment, without trailing slash. */ function dsp_jupyterhub_user_route(string $baseUrl, ?int $personId = null): string { $baseUrl = rtrim($baseUrl, '/'); $pathTemplate = getenv('JUPYTERHUB_USER_PATH'); if ($pathTemplate === false || $pathTemplate === '') { return $baseUrl; } $personId = $personId ?? (isset($_SESSION['person_id']) ? (int) $_SESSION['person_id'] : null); $username = dsp_jupyterhub_username($personId); if ($username === null) { return $baseUrl; } $relativePath = str_replace( ['{username}', '{person_id}'], [$username, (string) $personId], ltrim($pathTemplate, '/') ); return rtrim($baseUrl . '/' . $relativePath, '/'); } /** * Builds the iframe URL using the resolved base URL and token. * * @param string|null $baseUrl Optional override of the base URL. * @param string|null $token Optional override of the token. * @param int|null $personId Optional override of the person ID used for the route. * * @return string */ function dsp_jupyter_iframe_url(?string $baseUrl = null, ?string $token = null, ?int $personId = null): string { $resolvedBase = rtrim($baseUrl ?: dsp_jupyter_base_url(), '/'); $userRoute = dsp_jupyterhub_user_route($resolvedBase, $personId); $finalToken = $token ?: dsp_jupyter_token(); return $finalToken ? sprintf('%s?token=%s', $userRoute, urlencode($finalToken)) : $userRoute; } /** * Determines the host port the Jupyter service is published on. * * @return string */ function dsp_jupyter_port(): string { $configured = getenv('JUPYTER_PORT'); if ($configured === false || $configured === '') { $configured = getenv('JUPYTERHUB_PORT'); } if ($configured) { return (string) $configured; } $defaults = dsp_jupyter_defaults(); return $defaults['port']; } /** * Captures the environment overrides impacting the embedded Jupyter configuration. * * @return array */ function dsp_jupyter_env_overrides(): array { return [ 'JUPYTER_EXTERNAL_URL' => getenv('JUPYTER_EXTERNAL_URL') ?: null, 'JUPYTER_TOKEN' => getenv('JUPYTER_TOKEN') ?: null, 'JUPYTER_PORT' => getenv('JUPYTER_PORT') ?: null, 'JUPYTERHUB_PORT' => getenv('JUPYTERHUB_PORT') ?: null, 'DSP_APP_ORIGINS' => getenv('DSP_APP_ORIGINS') ?: null, 'DSP_FRAME_ANCESTORS' => getenv('DSP_FRAME_ANCESTORS') ?: null, 'JUPYTERHUB_USERNAME_TEMPLATE' => getenv('JUPYTERHUB_USERNAME_TEMPLATE') ?: null, 'JUPYTERHUB_USER_PATH' => getenv('JUPYTERHUB_USER_PATH') ?: null, ]; }