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'];
|
|
|
|
}
|
|
|
|
|
2024-08-26 18:09:02 +00:00
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|