syokuhou/src/AuthzContext.php
2025-01-14 01:45:03 +00:00

258 lines
7.7 KiB
PHP

<?php
namespace Syokuhou;
use RuntimeException;
use RPCii\Client\RpcClient;
use Index\Http\{HttpRequest,HttpResponseBuilder};
class AuthzContext {
public private(set) bool $authed = false;
public private(set) string $error = '';
public private(set) string $method = '';
public private(set) string $type = '';
public private(set) string $userId = '';
public private(set) string $appId = '';
/** @var ?mixed[] */
private ?array $scopeRaw = null;
private ?int $expiresAtRaw = null; // @phpstan-ignore-line: property hook skill issue
public function __construct(
private RpcClient $rpcClient
) {}
public bool $hasError {
get => $this->error !== '';
}
public bool $isBasicAuth {
get => strcasecmp('basic', $this->method) === 0;
}
public bool $isBearerAuth {
get => strcasecmp('bearer', $this->method) === 0;
}
public bool $isMisuzuAuth {
get => strcasecmp('misuzu', $this->method) === 0;
}
public bool $isRealUser {
get => strcasecmp('user', $this->type) === 0;
}
public bool $isAppUser {
get => strcasecmp('app', $this->type) === 0;
}
public bool $isUser {
get => $this->isRealUser
|| $this->isAppUser;
}
public bool $isPublicApp {
get => strcasecmp('pubapp', $this->type) === 0;
}
public bool $isConfidentialApp {
get => strcasecmp('confapp', $this->type) === 0;
}
public bool $isApp {
get => $this->isPublicApp
|| $this->isConfidentialApp;
}
public bool $hasWildcardScope {
get => $this->scopeRaw === null;
}
public string $scopeString {
get => $this->scopeRaw === null ? '' : implode(' ', $this->scopeRaw);
}
/** @var string[] */
public array $scope {
get => $this->scopeRaw ?? [];
}
public function hasScope(string $scope): bool {
return $this->isUser && ($this->hasWildcardScope || in_array($scope, $this->scopeRaw));
}
public int $expiresAt {
get => $this->expiresAtRaw ?? PHP_INT_MAX;
}
public bool $expired {
get => $this->expiresAtRaw !== null
&& $this->expiresAtRaw <= time();
}
private function handleRpcResponse(mixed $info): void {
if(!is_array($info)) {
$data = json_encode($info);
if(!$data) $data = 'data';
$this->error = $data;
return;
}
if(array_key_exists('method', $info) && is_string($info['method']))
$this->method = $info['method'];
if(array_key_exists('error', $info) && is_string($info['error']))
$this->error = $info['error'];
if(array_key_exists('type', $info) && is_string($info['type']))
$this->type = $info['type'];
if(array_key_exists('user', $info) && is_string($info['user']))
$this->userId = $info['user'];
if(array_key_exists('app', $info) && is_string($info['app']))
$this->appId = $info['app'];
if(array_key_exists('scope', $info) && is_array($info['scope']))
$this->scopeRaw = $info['scope'];
if(array_key_exists('expires', $info) && is_int($info['expires']))
$this->expiresAtRaw = $info['expires'];
}
private function attemptBasicAppAuthInternal(string $remoteAddr, string $clientId, string $clientSecret = ''): void {
try {
$this->handleRpcResponse($this->rpcClient->action('hanyuu:oauth2:attemptAppAuth', [
'remoteAddr' => $remoteAddr,
'clientId' => $clientId,
'clientSecret' => $clientSecret,
]));
} catch(RuntimeException $ex) {
$this->error = 'rpc';
}
}
public function attemptBasicAppAuth(string $remoteAddr, string $clientId, string $clientSecret = ''): void {
if($this->authed)
return;
$this->authed = true;
$this->attemptBasicAppAuthInternal($remoteAddr, $clientId, $clientSecret);
}
public function basicAppAuthMiddleware(HttpResponseBuilder $response, HttpRequest $request): void {
if($this->authed)
return;
$header = explode(' ', (string)$request->getHeaderLine('Authorization'), 2);
if(strcasecmp('basic', $header[0]) !== 0)
return;
$this->authed = true;
$this->method = $header[0];
$parts = base64_decode($header[1] ?? '');
if($parts === false) {
$this->error = 'format';
return;
}
$parts = explode(':', $parts, 2);
$this->attemptBasicAppAuthInternal(
(string)filter_input(INPUT_SERVER, 'REMOTE_ADDR'),
$parts[0],
$parts[1] ?? ''
);
}
private function attemptBearerTokenAuthInternal(string $remoteAddr, string $bearerToken): void {
try {
$this->handleRpcResponse($this->rpcClient->action(
'hanyuu:oauth2:attemptBearerAuth',
['remoteAddr' => $remoteAddr, 'token' => $bearerToken]
));
} catch(RuntimeException $ex) {
$this->error = 'rpc';
}
}
public function attemptBearerTokenAuth(string $remoteAddr, string $bearerToken): void {
if($this->authed)
return;
$this->authed = true;
$this->attemptBearerTokenAuthInternal($remoteAddr, $bearerToken);
}
public function bearerTokenAuthMiddleware(HttpResponseBuilder $response, HttpRequest $request): void {
if($this->authed)
return;
$header = explode(' ', (string)$request->getHeaderLine('Authorization'), 2);
if(strcasecmp('bearer', $header[0]) !== 0)
return;
$this->authed = true;
$this->method = $header[0];
$bearerToken = trim($header[1] ?? '');
if($bearerToken === '') {
$this->error = 'format';
return;
}
$this->attemptBearerTokenAuthInternal(
(string)filter_input(INPUT_SERVER, 'REMOTE_ADDR'),
$bearerToken
);
}
private function attemptMisuzuTokenAuthInternal(string $remoteAddr, string $misuzuToken): void {
try {
$this->handleRpcResponse($this->rpcClient->action(
'misuzu:auth:attemptMisuzuAuth',
['remoteAddr' => $remoteAddr, 'token' => $misuzuToken]
));
} catch(RuntimeException $ex) {
$this->error = 'rpc';
}
}
public function attemptMisuzuTokenAuth(string $remoteAddr, string $misuzuToken): void {
if($this->authed)
return;
$this->authed = true;
$this->attemptMisuzuTokenAuthInternal($remoteAddr, $misuzuToken);
}
public function misuzuTokenAuthMiddleware(HttpResponseBuilder $response, HttpRequest $request): void {
if($this->authed)
return;
$header = explode(' ', (string)$request->getHeaderLine('Authorization'), 2);
if(strcasecmp('misuzu', $header[0]) !== 0)
return;
$this->authed = true;
$this->method = $header[0];
$misuzuToken = trim($header[1] ?? '');
if($misuzuToken === '') {
$this->error = 'format';
return;
}
$this->attemptMisuzuTokenAuthInternal(
(string)filter_input(INPUT_SERVER, 'REMOTE_ADDR'),
$misuzuToken
);
}
public function misuzuCookieAuthMiddleware(HttpResponseBuilder $response, HttpRequest $request): void {
if($this->authed || !$request->hasCookie('msz_auth'))
return;
$this->attemptMisuzuTokenAuthInternal(
(string)filter_input(INPUT_SERVER, 'REMOTE_ADDR'),
$request->getCookie('msz_auth')
);
}
}