212 lines
6.6 KiB
PHP
212 lines
6.6 KiB
PHP
<?php
|
|
|
|
if (!function_exists('view')) {
|
|
function view(string $view, array $data = []): string
|
|
{
|
|
$basedir = plugin_dir_path(__FILE__);
|
|
extract($data, EXTR_SKIP);
|
|
ob_start();
|
|
include "{$basedir}resources/views/$view.php";
|
|
|
|
return ob_get_clean();
|
|
}
|
|
}
|
|
|
|
if (!function_exists('mahak_debug_enabled')) {
|
|
function mahak_debug_enabled(): bool
|
|
{
|
|
return (bool) get_configured_option('debug-mode', false);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('mahak_render_error')) {
|
|
function mahak_render_error(
|
|
\Throwable $exception,
|
|
int $status = 500,
|
|
string $fallbackView = 'server-error',
|
|
array $context = []
|
|
): void
|
|
{
|
|
if (mahak_debug_enabled()) {
|
|
wp_die(
|
|
view('debug-error', ['report' => mahak_build_debug_report($exception, $context)]),
|
|
'Mahak Authentication Error',
|
|
['response' => $status]
|
|
);
|
|
}
|
|
|
|
wp_die(view($fallbackView), 'Authentication Error', ['response' => $status]);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('mahak_build_debug_report')) {
|
|
function mahak_build_debug_report(\Throwable $exception, array $context = []): array
|
|
{
|
|
$now = time();
|
|
$wordpressTimezone = function_exists('wp_timezone_string')
|
|
? wp_timezone_string()
|
|
: (string) get_option('timezone_string', '');
|
|
|
|
if ($wordpressTimezone === '') {
|
|
$wordpressTimezone = 'UTC offset '.(string) get_option('gmt_offset', 0);
|
|
}
|
|
|
|
$server = [
|
|
'Unix timestamp' => (string) $now,
|
|
'UTC time' => gmdate('Y-m-d H:i:s \U\T\C', $now),
|
|
'PHP local time' => date('Y-m-d H:i:s P T', $now),
|
|
'PHP timezone' => date_default_timezone_get(),
|
|
'PHP date.timezone' => (string) (ini_get('date.timezone') ?: '[not set]'),
|
|
'WordPress timezone' => $wordpressTimezone,
|
|
'WordPress version' => get_bloginfo('version'),
|
|
'PHP version' => PHP_VERSION,
|
|
'Plugin version' => defined('MAHAK_AUTHENTICATE_BRIDGE_VERSION')
|
|
? MAHAK_AUTHENTICATE_BRIDGE_VERSION
|
|
: '[unknown]',
|
|
];
|
|
|
|
$jwtTimestamps = [];
|
|
|
|
foreach (($context['jwt_timestamps'] ?? []) as $claim => $value) {
|
|
$jwtTimestamps[strtoupper((string) $claim)] = mahak_format_jwt_timestamp((string) $value, $now);
|
|
}
|
|
|
|
$trace = mahak_sanitized_exception_trace($exception);
|
|
$exceptionData = [
|
|
'Type' => get_class($exception),
|
|
'Message' => $exception->getMessage(),
|
|
'Location' => $exception->getFile().':'.$exception->getLine(),
|
|
];
|
|
|
|
$markdown = "# Mahak Authentication Error\n\n## Exception\n\n";
|
|
|
|
foreach ($exceptionData as $label => $value) {
|
|
$markdown .= '- '.$label.': `'.mahak_markdown_value($value)."`\n";
|
|
}
|
|
|
|
$markdown .= "\n## WordPress server diagnostics\n\n| Field | Value |\n|---|---|\n";
|
|
|
|
foreach ($server as $label => $value) {
|
|
$markdown .= '| '.mahak_markdown_value($label).' | `'.mahak_markdown_value($value)."` |\n";
|
|
}
|
|
|
|
$markdown .= "\n## JWT timing claims\n\n";
|
|
|
|
if ($jwtTimestamps === []) {
|
|
$markdown .= "No readable JWT timing claims were available.\n";
|
|
} else {
|
|
$markdown .= "| Claim | Value |\n|---|---|\n";
|
|
|
|
foreach ($jwtTimestamps as $label => $value) {
|
|
$markdown .= '| '.mahak_markdown_value($label).' | `'.mahak_markdown_value($value)."` |\n";
|
|
}
|
|
}
|
|
|
|
$markdown .= "\n## Sanitized stack trace\n\n```text\n".str_replace('```', "'''", $trace)."\n```\n";
|
|
$markdown .= "\n> JWT contents, request parameters, and function arguments are intentionally omitted.\n";
|
|
|
|
return [
|
|
'exception' => $exceptionData,
|
|
'server' => $server,
|
|
'jwt_timestamps' => $jwtTimestamps,
|
|
'trace' => $trace,
|
|
'markdown' => $markdown,
|
|
];
|
|
}
|
|
}
|
|
|
|
if (!function_exists('mahak_format_jwt_timestamp')) {
|
|
function mahak_format_jwt_timestamp(string $value, int $serverNow): string
|
|
{
|
|
if (!is_numeric($value)) {
|
|
return $value.' (not numeric)';
|
|
}
|
|
|
|
$numericValue = (float) $value;
|
|
$note = '';
|
|
|
|
if (is_infinite($numericValue) || is_nan($numericValue)) {
|
|
return $value.' (invalid numeric value)';
|
|
}
|
|
|
|
if (abs($numericValue) >= 100000000000) {
|
|
$numericValue /= 1000;
|
|
$note = '; appears to use milliseconds';
|
|
}
|
|
|
|
$timestamp = (int) $numericValue;
|
|
$difference = $timestamp - $serverNow;
|
|
$differenceLabel = ($difference >= 0 ? '+' : '').$difference.' seconds vs server';
|
|
|
|
return $value.' ('.gmdate('Y-m-d H:i:s \U\T\C', $timestamp).'; '.$differenceLabel.$note.')';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('mahak_sanitized_exception_trace')) {
|
|
function mahak_sanitized_exception_trace(\Throwable $exception): string
|
|
{
|
|
$lines = [];
|
|
|
|
foreach ($exception->getTrace() as $index => $frame) {
|
|
$location = isset($frame['file'])
|
|
? $frame['file'].':'.($frame['line'] ?? '?')
|
|
: '[internal function]';
|
|
$call = ($frame['class'] ?? '').($frame['type'] ?? '').($frame['function'] ?? '[unknown]').'()';
|
|
$lines[] = '#'.$index.' '.$location.' '.$call;
|
|
}
|
|
|
|
$lines[] = '#'.count($lines).' {main}';
|
|
|
|
return implode("\n", $lines);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('mahak_markdown_value')) {
|
|
function mahak_markdown_value(string $value): string
|
|
{
|
|
return str_replace(["\r", "\n", '|', '`'], [' ', ' ', '\\|', "'"], $value);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('dd')) {
|
|
function dd(): void
|
|
{
|
|
echo '<pre>';
|
|
array_map(function ($arg) {
|
|
var_dump($arg);
|
|
}, func_get_args());
|
|
die;
|
|
}
|
|
}
|
|
|
|
function setup_configs(): void
|
|
{
|
|
$configs = [];
|
|
$configs_dir = plugin_dir_path(__FILE__).'configs';
|
|
foreach (glob($configs_dir.'/*.php') as $configs_file) {
|
|
$configs[basename($configs_file, '.php')] = include $configs_file;
|
|
}
|
|
|
|
define("MAHAK_CASHED_CONFIG", $configs);
|
|
}
|
|
setup_configs();
|
|
|
|
if (!function_exists('config')) {
|
|
function config(string $key, ?string $default = null)
|
|
{
|
|
$value = MAHAK_CASHED_CONFIG;
|
|
foreach (explode('.', $key) as $key) {
|
|
$value = $value[$key] ?? $default;
|
|
}
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
|
|
if (!function_exists('get_configured_option')) {
|
|
function get_configured_option(string $key, ?string $default = null)
|
|
{
|
|
return get_option(config("mahak.options.$key"), $default);
|
|
}
|
|
}
|