258 lines
7.7 KiB
PHP
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')
|
|
);
|
|
}
|
|
}
|