syokuhou/src/AuthzContext.php

282 lines
7.9 KiB
PHP
Raw Normal View History

2024-08-25 23:04:11 +00:00
<?php
2024-11-14 01:22:18 +00:00
namespace Syokuhou;
2024-08-25 23:04:11 +00:00
2024-11-14 00:38:12 +00:00
use RuntimeException;
use RPCii\Client\RpcClient;
2024-08-25 23:04:11 +00:00
class AuthzContext {
private bool $authed = false;
private string $error = '';
private string $method = '';
private string $type = '';
private string $userId = '';
private string $appId = '';
private ?array $scope = null;
private ?int $expires = null;
public function __construct(
2024-11-14 00:38:12 +00:00
private RpcClient $rpcClient
2024-08-25 23:04:11 +00:00
) {}
public function hasAuthed(): bool {
return $this->authed;
}
public function hasError(): bool {
return $this->error !== '';
}
public function getError(): string {
return $this->error;
}
public function getMethod(): string {
return $this->method;
}
public function isBasicAuth(): bool {
return strcasecmp('basic', $this->method) === 0;
}
public function isBearerAuth(): bool {
return strcasecmp('bearer', $this->method) === 0;
}
public function isMisuzuAuth(): bool {
return strcasecmp('misuzu', $this->method) === 0;
}
public function getType(): string {
return $this->type;
}
public function isRealUser(): bool {
return strcasecmp('user', $this->type) === 0;
}
public function isAppUser(): bool {
return strcasecmp('app', $this->type) === 0;
}
public function isUser(): bool {
return $this->isRealUser()
|| $this->isAppUser();
}
public function isPublicApp(): bool {
return strcasecmp('pubapp', $this->type) === 0;
}
public function isConfidentialApp(): bool {
return strcasecmp('confapp', $this->type) === 0;
}
public function isApp(): bool {
return $this->isPublicApp()
|| $this->isConfidentialApp();
}
public function getAppId(): string {
return $this->appId;
}
public function getUserId(): string {
return $this->userId;
}
public function hasWildcardScope(): bool {
return $this->scope === null;
}
public function getScopeString(): string {
return $this->scope === null ? '' : implode(' ', $this->scope);
}
public function getScopeArray(): array {
return $this->scope ?? [];
}
public function hasScope(string $scope): bool {
return $this->scope === null
|| in_array($scope, $this->scope);
}
public function getExpiresAt(): int {
return $this->expires ?? PHP_INT_MAX;
}
public function hasExpired(): bool {
return $this->expires !== null
&& $this->expires <= time();
}
private function handleRpcResponse(mixed $info): void {
if(!is_array($info)) {
$this->error = json_encode($info);
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->scope = $info['scope'];
2024-11-14 00:38:12 +00:00
if(array_key_exists('expires', $info) && is_int($info['expires']))
2024-08-25 23:04:11 +00:00
$this->expires = $info['expires'];
}
public function dump(): array {
return [
'authed' => $this->authed,
'error' => $this->error,
'method' => $this->method,
'type' => $this->type,
'user' => $this->userId,
'app' => $this->appId,
'scope' => $this->scope,
'expires' => $this->expires,
];
}
2024-08-25 23:04:11 +00:00
private function attemptBasicAppAuthInternal(string $remoteAddr, string $clientId, string $clientSecret = ''): void {
try {
2024-11-14 00:38:12 +00:00
$this->handleRpcResponse($this->rpcClient->action('hanyuu:oauth2:attemptAppAuth', [
2024-08-25 23:04:11 +00:00
'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($response, $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 {
2024-11-14 00:38:12 +00:00
$this->handleRpcResponse($this->rpcClient->action(
2024-08-25 23:04:11 +00:00
'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($response, $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 {
2024-11-14 00:38:12 +00:00
$this->handleRpcResponse($this->rpcClient->action(
2024-08-25 23:04:11 +00:00
'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($response, $request) {
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
);
}
}