Files
wordpress-theme-aksrt/src/Auth/OAuthProxyProtocol.php
T
2026-08-29 22:03:48 +08:00

165 lines
4.4 KiB
PHP

<?php
/**
* @package AKSRT
*/
namespace AKSRT\Auth;
defined('ABSPATH') || exit;
final class OAuthProxyProtocol {
public const VERSION = '2';
public const MAX_CLOCK_SKEW = 300;
public const REPLAY_TTL = 660;
private const REQUEST_DIRECTION = 'request';
private const CALLBACK_DIRECTION = 'callback';
private const REQUEST_FIELDS = [
'v',
'request_id',
'provider',
'action',
'agent_back_url',
'ts',
];
private const CALLBACK_FIELDS = [
'v',
'request_id',
'jti',
'provider',
'action',
'issuer',
'provider_id',
'username',
'name',
'email',
'email_verified',
'avatar_url',
'url',
'bio',
'locale',
'error',
'ts',
];
public static function request_fields(array $source): array {
return self::fields($source, self::REQUEST_FIELDS);
}
public static function callback_fields(array $source): array {
return self::fields($source, self::CALLBACK_FIELDS);
}
public static function sign_request(array $fields, string $secret): string {
return self::sign(self::request_fields($fields), $secret, self::REQUEST_DIRECTION);
}
public static function verify_request(array $fields, string $secret, string $signature): bool {
return self::verify(self::request_fields($fields), $secret, self::REQUEST_DIRECTION, $signature);
}
public static function sign_callback(array $fields, string $secret): string {
return self::sign(self::callback_fields($fields), $secret, self::CALLBACK_DIRECTION);
}
public static function verify_callback(array $fields, string $secret, string $signature): bool {
return self::verify(self::callback_fields($fields), $secret, self::CALLBACK_DIRECTION, $signature);
}
public static function timestamp_valid(string $timestamp): bool {
if (preg_match('/^[0-9]{10}$/', $timestamp) !== 1) {
return false;
}
return abs(time() - (int) $timestamp) <= self::MAX_CLOCK_SKEW;
}
public static function token_valid(string $token): bool {
return preg_match('/^[A-Za-z0-9_-]{32,128}$/', $token) === 1;
}
public static function callback_url(string $url): string {
$url = OAuthUrlPolicy::public_https($url);
if ($url === '') {
return '';
}
$parts = wp_parse_url($url);
$path = rtrim((string) ($parts['path'] ?? ''), '/');
if (!is_array($parts) || !str_ends_with($path, '/oauth/proxy/callback') || isset($parts['query'])) {
return '';
}
$authority = self::authority($parts);
return $authority !== '' ? 'https://' . $authority . $path . '/' : '';
}
public static function server_base_url(string $url): string {
$url = OAuthUrlPolicy::public_https($url);
if ($url === '') {
return '';
}
$parts = wp_parse_url($url);
if (!is_array($parts) || isset($parts['query'])) {
return '';
}
$authority = self::authority($parts);
if ($authority === '') {
return '';
}
$path = rtrim((string) ($parts['path'] ?? ''), '/');
return 'https://' . $authority . $path;
}
public static function issuer(string $url): string {
$url = OAuthUrlPolicy::public_https($url);
$parts = $url !== '' ? wp_parse_url($url) : false;
if (!is_array($parts)) {
return '';
}
$authority = self::authority($parts);
return $authority !== '' ? 'https://' . $authority : '';
}
private static function authority(array $parts): string {
$host = strtolower((string) ($parts['host'] ?? ''));
if ($host === '') {
return '';
}
if (str_contains($host, ':') && $host[0] !== '[') {
$host = '[' . $host . ']';
}
$port = isset($parts['port']) ? ':' . (int) $parts['port'] : '';
return $host . $port;
}
private static function fields(array $source, array $names): array {
$fields = [];
foreach ($names as $name) {
$value = $source[$name] ?? '';
if (!is_scalar($value) && $value !== null) {
$value = '';
}
$fields[$name] = (string) wp_unslash((string) $value);
}
return $fields;
}
private static function sign(array $fields, string $secret, string $direction): string {
if ($secret === '') {
return '';
}
ksort($fields, SORT_STRING);
$payload = http_build_query($fields, '', '&', PHP_QUERY_RFC3986);
$key = hash_hmac('sha256', 'aksrt-oauth-proxy-v2/' . $direction, $secret, true);
return hash_hmac('sha256', $payload, $key);
}
private static function verify(array $fields, string $secret, string $direction, string $signature): bool {
if ($secret === '' || preg_match('/^[a-f0-9]{64}$/i', $signature) !== 1) {
return false;
}
return hash_equals(self::sign($fields, $secret, $direction), strtolower($signature));
}
}