Implemented core OAuth spec token flows.
This commit is contained in:
parent
1a0456462c
commit
3ea982fd43
11 changed files with 362 additions and 152 deletions
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Hanyuu;
|
namespace Hanyuu;
|
||||||
|
|
||||||
use Index\Autoloader;
|
|
||||||
use Index\Environment;
|
use Index\Environment;
|
||||||
use Index\Data\DbTools;
|
use Index\Data\DbTools;
|
||||||
use Syokuhou\SharpConfig;
|
use Syokuhou\SharpConfig;
|
||||||
|
@ -19,10 +18,8 @@ define('HAU_DIR_TEMPLATES', HAU_ROOT . '/templates');
|
||||||
require_once HAU_ROOT . '/vendor/autoload.php';
|
require_once HAU_ROOT . '/vendor/autoload.php';
|
||||||
|
|
||||||
Environment::setDebug(HAU_DEBUG);
|
Environment::setDebug(HAU_DEBUG);
|
||||||
|
|
||||||
mb_internal_encoding('utf-8');
|
mb_internal_encoding('utf-8');
|
||||||
date_default_timezone_set('utc');
|
date_default_timezone_set('utc');
|
||||||
set_include_path(get_include_path() . PATH_SEPARATOR . HAU_ROOT);
|
|
||||||
|
|
||||||
$cfg = SharpConfig::fromFile(HAU_ROOT . '/hanyuu.cfg');
|
$cfg = SharpConfig::fromFile(HAU_ROOT . '/hanyuu.cfg');
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ class AppInfo {
|
||||||
private bool $trusted,
|
private bool $trusted,
|
||||||
private string $type,
|
private string $type,
|
||||||
private string $clientId,
|
private string $clientId,
|
||||||
private string $clientSecret, // should this be stored hashed?
|
private string $clientSecret,
|
||||||
private int $created,
|
private int $created,
|
||||||
private int $updated,
|
private int $updated,
|
||||||
private ?int $deleted
|
private ?int $deleted
|
||||||
|
|
|
@ -15,7 +15,7 @@ class AppsData {
|
||||||
$this->cache = new DbStatementCache($dbConn);
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getApp(
|
public function getAppInfo(
|
||||||
?string $appId = null,
|
?string $appId = null,
|
||||||
?string $clientId = null,
|
?string $clientId = null,
|
||||||
?bool $deleted = null
|
?bool $deleted = null
|
||||||
|
@ -53,7 +53,7 @@ class AppsData {
|
||||||
return $result->next() ? $result->getInteger(0) : 0;
|
return $result->next() ? $result->getInteger(0) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAppUris(AppInfo|string $appInfo): array {
|
public function getAppUriInfos(AppInfo|string $appInfo): array {
|
||||||
$stmt = $this->cache->get('SELECT uri_id, app_id, uri_string, UNIX_TIMESTAMP(uri_created) FROM hau_apps_uris WHERE app_id = ?');
|
$stmt = $this->cache->get('SELECT uri_id, app_id, uri_string, UNIX_TIMESTAMP(uri_created) FROM hau_apps_uris WHERE app_id = ?');
|
||||||
$stmt->addParameter(1, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo);
|
$stmt->addParameter(1, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
@ -66,7 +66,7 @@ class AppsData {
|
||||||
return $infos;
|
return $infos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAppUri(string $uriId): AppUriInfo {
|
public function getAppUriInfo(string $uriId): AppUriInfo {
|
||||||
$stmt = $this->cache->get('SELECT uri_id, app_id, uri_string, UNIX_TIMESTAMP(uri_created) FROM hau_apps_uris WHERE uri_id = ?');
|
$stmt = $this->cache->get('SELECT uri_id, app_id, uri_string, UNIX_TIMESTAMP(uri_created) FROM hau_apps_uris WHERE uri_id = ?');
|
||||||
$stmt->addParameter(1, $uriId);
|
$stmt->addParameter(1, $uriId);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
|
@ -11,6 +11,14 @@ class MisuzuInterop {
|
||||||
private IConfig $config
|
private IConfig $config
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
private function getEndpoint(): string {
|
||||||
|
return $this->config->getString('endpoint');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSecret(): string {
|
||||||
|
return $this->config->getString('secret');
|
||||||
|
}
|
||||||
|
|
||||||
public function authCheck(string $method, string $token, string $remoteAddr, array $avatars = []): object {
|
public function authCheck(string $method, string $token, string $remoteAddr, array $avatars = []): object {
|
||||||
$result = $this->callRpc('auth-check', body: [
|
$result = $this->callRpc('auth-check', body: [
|
||||||
'method' => $method,
|
'method' => $method,
|
||||||
|
@ -28,7 +36,7 @@ class MisuzuInterop {
|
||||||
private function callRpc(string $action, array $params = [], array $body = []): object {
|
private function callRpc(string $action, array $params = [], array $body = []): object {
|
||||||
$time = time();
|
$time = time();
|
||||||
|
|
||||||
$url = sprintf('%s/_hanyuu/%s', $this->config->getString('endpoint'), $action);
|
$url = sprintf('%s/_hanyuu/%s', $this->getEndpoint(), $action);
|
||||||
if(empty($params))
|
if(empty($params))
|
||||||
$params = '';
|
$params = '';
|
||||||
else {
|
else {
|
||||||
|
@ -42,7 +50,7 @@ class MisuzuInterop {
|
||||||
$signature = UriBase64::encode(hash_hmac(
|
$signature = UriBase64::encode(hash_hmac(
|
||||||
'sha256',
|
'sha256',
|
||||||
sprintf('[%s|%s|%s]', $time, $url, $body),
|
sprintf('[%s|%s|%s]', $time, $url, $body),
|
||||||
$this->config->getString('secret'),
|
$this->getSecret(),
|
||||||
true
|
true
|
||||||
));
|
));
|
||||||
$headers = [
|
$headers = [
|
||||||
|
|
|
@ -7,7 +7,7 @@ class OAuth2AccessInfo {
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $id,
|
private string $id,
|
||||||
private string $appId,
|
private string $appId,
|
||||||
private string $userId,
|
private ?string $userId,
|
||||||
private string $token,
|
private string $token,
|
||||||
private string $scope,
|
private string $scope,
|
||||||
private int $created,
|
private int $created,
|
||||||
|
@ -18,7 +18,7 @@ class OAuth2AccessInfo {
|
||||||
return new OAuth2AccessInfo(
|
return new OAuth2AccessInfo(
|
||||||
id: $result->getString(0),
|
id: $result->getString(0),
|
||||||
appId: $result->getString(1),
|
appId: $result->getString(1),
|
||||||
userId: $result->getString(2),
|
userId: $result->getStringOrNull(2),
|
||||||
token: $result->getString(3),
|
token: $result->getString(3),
|
||||||
scope: $result->getString(4),
|
scope: $result->getString(4),
|
||||||
created: $result->getInteger(5),
|
created: $result->getInteger(5),
|
||||||
|
@ -34,7 +34,10 @@ class OAuth2AccessInfo {
|
||||||
return $this->appId;
|
return $this->appId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserId(): string {
|
public function hasUserId(): bool {
|
||||||
|
return $this->userId !== null;
|
||||||
|
}
|
||||||
|
public function getUserId(): ?string {
|
||||||
return $this->userId;
|
return $this->userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use Index\Data\IDbConnection;
|
||||||
use Hanyuu\Apps\AppInfo;
|
use Hanyuu\Apps\AppInfo;
|
||||||
use Hanyuu\Apps\AppUriInfo;
|
use Hanyuu\Apps\AppUriInfo;
|
||||||
|
|
||||||
class OAuth2RequestsData {
|
class OAuth2AuthoriseData {
|
||||||
private IDbConnection $dbConn;
|
private IDbConnection $dbConn;
|
||||||
private DbStatementCache $cache;
|
private DbStatementCache $cache;
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ class OAuth2RequestsData {
|
||||||
$this->cache = new DbStatementCache($dbConn);
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRequest(
|
public function getAuthoriseInfo(
|
||||||
?string $requestId = null,
|
?string $authoriseId = null,
|
||||||
AppInfo|string|null $appInfo = null,
|
AppInfo|string|null $appInfo = null,
|
||||||
?string $userId = null,
|
?string $userId = null,
|
||||||
AppUriInfo|string|null $appUriInfo = null,
|
AppUriInfo|string|null $appUriInfo = null,
|
||||||
|
@ -28,12 +28,12 @@ class OAuth2RequestsData {
|
||||||
?string $challengeMethod = null,
|
?string $challengeMethod = null,
|
||||||
?string $scope = null,
|
?string $scope = null,
|
||||||
?string $code = null
|
?string $code = null
|
||||||
): OAuth2RequestInfo {
|
): OAuth2AuthoriseInfo {
|
||||||
$selectors = [];
|
$selectors = [];
|
||||||
$values = [];
|
$values = [];
|
||||||
if($requestId !== null) {
|
if($authoriseId !== null) {
|
||||||
$selectors[] = 'req_id = ?';
|
$selectors[] = 'auth_id = ?';
|
||||||
$values[] = $requestId;
|
$values[] = $authoriseId;
|
||||||
}
|
}
|
||||||
if($appInfo !== null) {
|
if($appInfo !== null) {
|
||||||
$selectors[] = 'app_id = ?';
|
$selectors[] = 'app_id = ?';
|
||||||
|
@ -48,43 +48,43 @@ class OAuth2RequestsData {
|
||||||
$values[] = $appUriInfo instanceof AppUriInfo ? $appUriInfo->getId() : $appUriInfo;
|
$values[] = $appUriInfo instanceof AppUriInfo ? $appUriInfo->getId() : $appUriInfo;
|
||||||
}
|
}
|
||||||
if($state !== null) {
|
if($state !== null) {
|
||||||
$selectors[] = 'req_state = ?';
|
$selectors[] = 'auth_state = ?';
|
||||||
$values[] = $state;
|
$values[] = $state;
|
||||||
}
|
}
|
||||||
if($challengeCode !== null) {
|
if($challengeCode !== null) {
|
||||||
$selectors[] = 'req_challenge_code = ?';
|
$selectors[] = 'auth_challenge_code = ?';
|
||||||
$values[] = $challengeCode;
|
$values[] = $challengeCode;
|
||||||
}
|
}
|
||||||
if($challengeMethod !== null) {
|
if($challengeMethod !== null) {
|
||||||
$selectors[] = 'req_challenge_method = ?';
|
$selectors[] = 'auth_challenge_method = ?';
|
||||||
$values[] = $challengeMethod;
|
$values[] = $challengeMethod;
|
||||||
}
|
}
|
||||||
if($scope !== null) {
|
if($scope !== null) {
|
||||||
$selectors[] = 'req_scope = ?';
|
$selectors[] = 'auth_scope = ?';
|
||||||
$values[] = $scope;
|
$values[] = $scope;
|
||||||
}
|
}
|
||||||
if($code !== null) {
|
if($code !== null) {
|
||||||
$selectors[] = 'req_code = ?';
|
$selectors[] = 'auth_code = ?';
|
||||||
$values[] = $code;
|
$values[] = $code;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(empty($selectors))
|
if(empty($selectors))
|
||||||
throw new RuntimeException('Insufficient data to do request lookup.');
|
throw new RuntimeException('Insufficient data to do authorisation request lookup.');
|
||||||
|
|
||||||
$args = 0;
|
$args = 0;
|
||||||
$stmt = $this->cache->get('SELECT req_id, app_id, user_id, uri_id, req_state, req_challenge_code, req_challenge_method, req_scope, req_code, req_approval, UNIX_TIMESTAMP(req_created) FROM hau_oauth2_requests WHERE ' . implode(' AND ', $selectors));
|
$stmt = $this->cache->get('SELECT auth_id, app_id, user_id, uri_id, auth_state, auth_challenge_code, auth_challenge_method, auth_scope, auth_code, auth_approval, UNIX_TIMESTAMP(auth_created) FROM hau_oauth2_authorise WHERE ' . implode(' AND ', $selectors));
|
||||||
foreach($values as $value)
|
foreach($values as $value)
|
||||||
$stmt->addParameter(++$args, $value);
|
$stmt->addParameter(++$args, $value);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
$result = $stmt->getResult();
|
$result = $stmt->getResult();
|
||||||
if(!$result->next())
|
if(!$result->next())
|
||||||
throw new RuntimeException('Request not found.');
|
throw new RuntimeException('Authorise request not found.');
|
||||||
|
|
||||||
return OAuth2RequestInfo::fromResult($result);
|
return OAuth2AuthoriseInfo::fromResult($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createRequest(
|
public function createAuthorise(
|
||||||
AppInfo|string $appInfo,
|
AppInfo|string $appInfo,
|
||||||
string $userId,
|
string $userId,
|
||||||
AppUriInfo|string $appUriInfo,
|
AppUriInfo|string $appUriInfo,
|
||||||
|
@ -92,8 +92,8 @@ class OAuth2RequestsData {
|
||||||
string $challengeCode,
|
string $challengeCode,
|
||||||
string $challengeMethod,
|
string $challengeMethod,
|
||||||
string $scope
|
string $scope
|
||||||
): OAuth2RequestInfo {
|
): OAuth2AuthoriseInfo {
|
||||||
$stmt = $this->cache->get('INSERT INTO hau_oauth2_requests (app_id, user_id, uri_id, req_state, req_challenge_code, req_challenge_method, req_scope, req_code) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
|
$stmt = $this->cache->get('INSERT INTO hau_oauth2_authorise (app_id, user_id, uri_id, auth_state, auth_challenge_code, auth_challenge_method, auth_scope, auth_code) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
$stmt->addParameter(1, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo);
|
$stmt->addParameter(1, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo);
|
||||||
$stmt->addParameter(2, $userId);
|
$stmt->addParameter(2, $userId);
|
||||||
$stmt->addParameter(3, $appUriInfo instanceof AppUriInfo ? $appUriInfo->getId() : $appUriInfo);
|
$stmt->addParameter(3, $appUriInfo instanceof AppUriInfo ? $appUriInfo->getId() : $appUriInfo);
|
||||||
|
@ -104,20 +104,20 @@ class OAuth2RequestsData {
|
||||||
$stmt->addParameter(8, XString::random(100));
|
$stmt->addParameter(8, XString::random(100));
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
return $this->getRequest(requestId: (string)$this->dbConn->getLastInsertId());
|
return $this->getAuthoriseInfo(authoriseId: (string)$this->dbConn->getLastInsertId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteRequests(
|
public function deleteAuthorise(
|
||||||
OAuth2RequestInfo|string|null $requestInfo = null,
|
OAuth2AuthoriseInfo|string|null $authoriseId = null,
|
||||||
AppInfo|string|null $appInfo = null,
|
AppInfo|string|null $appInfo = null,
|
||||||
?string $userId = null,
|
?string $userId = null,
|
||||||
?string $code = null
|
?string $code = null
|
||||||
): void {
|
): void {
|
||||||
$selectors = [];
|
$selectors = [];
|
||||||
$values = [];
|
$values = [];
|
||||||
if($requestInfo !== null) {
|
if($authoriseId !== null) {
|
||||||
$selectors[] = 'req_id = ?';
|
$selectors[] = 'auth_id = ?';
|
||||||
$values[] = $requestInfo instanceof OAuth2RequestInfo ? $requestInfo->getId() : $requestInfo;
|
$values[] = $authoriseId instanceof OAuth2AuthoriseInfo ? $authoriseId->getId() : $authoriseId;
|
||||||
}
|
}
|
||||||
if($appInfo !== null) {
|
if($appInfo !== null) {
|
||||||
$selectors[] = 'app_id = ?';
|
$selectors[] = 'app_id = ?';
|
||||||
|
@ -128,11 +128,11 @@ class OAuth2RequestsData {
|
||||||
$values[] = $userId;
|
$values[] = $userId;
|
||||||
}
|
}
|
||||||
if($code !== null) {
|
if($code !== null) {
|
||||||
$selectors[] = 'req_code = ?';
|
$selectors[] = 'auth_code = ?';
|
||||||
$values[] = $code;
|
$values[] = $code;
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = 'DELETE FROM hau_oauth2_requests';
|
$query = 'DELETE FROM hau_oauth2_authorise';
|
||||||
if(!empty($selectors))
|
if(!empty($selectors))
|
||||||
$query .= sprintf(' WHERE %s', implode(' AND ', $selectors));
|
$query .= sprintf(' WHERE %s', implode(' AND ', $selectors));
|
||||||
|
|
||||||
|
@ -143,17 +143,17 @@ class OAuth2RequestsData {
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setRequestApproval(OAuth2RequestInfo|string $requestInfo, bool $approval): void {
|
public function setAuthoriseApproval(OAuth2AuthoriseInfo|string $authoriseInfo, bool $approval): void {
|
||||||
if($requestInfo instanceof OAuth2RequestInfo) {
|
if($authoriseInfo instanceof OAuth2AuthoriseInfo) {
|
||||||
if(!$requestInfo->isPending())
|
if(!$authoriseInfo->isPending())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
$requestInfo = $requestInfo->getId();
|
$authoriseInfo = $authoriseInfo->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $this->cache->get('UPDATE hau_oauth2_requests SET req_approval = ? WHERE req_id = ? AND req_approval = "pending"');
|
$stmt = $this->cache->get('UPDATE hau_oauth2_authorise SET auth_approval = ? WHERE auth_id = ? AND auth_approval = "pending"');
|
||||||
$stmt->addParameter(1, $approval ? 'approved' : 'denied');
|
$stmt->addParameter(1, $approval ? 'approved' : 'denied');
|
||||||
$stmt->addParameter(2, $requestInfo);
|
$stmt->addParameter(2, $authoriseInfo);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ namespace Hanyuu\OAuth2;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Index\Serialisation\UriBase64;
|
use Index\Serialisation\UriBase64;
|
||||||
|
|
||||||
class OAuth2RequestInfo {
|
class OAuth2AuthoriseInfo {
|
||||||
private const EXPIRES_TIME = 10 * 60;
|
private const EXPIRES_TIME = 10 * 60;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -21,8 +21,8 @@ class OAuth2RequestInfo {
|
||||||
private int $created
|
private int $created
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public static function fromResult(IDbResult $result): OAuth2RequestInfo {
|
public static function fromResult(IDbResult $result): OAuth2AuthoriseInfo {
|
||||||
return new OAuth2RequestInfo(
|
return new OAuth2AuthoriseInfo(
|
||||||
id: $result->getString(0),
|
id: $result->getString(0),
|
||||||
appId: $result->getString(1),
|
appId: $result->getString(1),
|
||||||
userId: $result->getString(2),
|
userId: $result->getString(2),
|
||||||
|
@ -57,10 +57,6 @@ class OAuth2RequestInfo {
|
||||||
return $this->state;
|
return $this->state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hasCodeChallenge(): bool {
|
|
||||||
return $this->challengeCode !== '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getChallengeCode(): string {
|
public function getChallengeCode(): string {
|
||||||
return $this->challengeCode;
|
return $this->challengeCode;
|
||||||
}
|
}
|
|
@ -5,22 +5,37 @@ use Index\Data\IDbConnection;
|
||||||
use Hanyuu\Apps\AppInfo;
|
use Hanyuu\Apps\AppInfo;
|
||||||
|
|
||||||
class OAuth2Context {
|
class OAuth2Context {
|
||||||
private OAuth2RequestsData $requests;
|
private OAuth2AuthoriseData $authorise;
|
||||||
private OAuth2TokensData $tokens;
|
private OAuth2TokensData $tokens;
|
||||||
|
|
||||||
public function __construct(IDbConnection $dbConn) {
|
public function __construct(IDbConnection $dbConn) {
|
||||||
$this->requests = new OAuth2RequestsData($dbConn);
|
$this->authorise = new OAuth2AuthoriseData($dbConn);
|
||||||
$this->tokens = new OAuth2TokensData($dbConn);
|
$this->tokens = new OAuth2TokensData($dbConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRequestsData(): OAuth2RequestsData {
|
public function getAuthoriseData(): OAuth2AuthoriseData {
|
||||||
return $this->requests;
|
return $this->authorise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTokensData(): OAuth2TokensData {
|
public function getTokensData(): OAuth2TokensData {
|
||||||
return $this->tokens;
|
return $this->tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createRefreshFromAccessInfo(
|
||||||
|
OAuth2AccessInfo $accessInfo,
|
||||||
|
?string $token = null,
|
||||||
|
?int $lifetime = null
|
||||||
|
): OAuth2RefreshInfo {
|
||||||
|
return $this->tokens->createRefresh(
|
||||||
|
$accessInfo->getAppId(),
|
||||||
|
$accessInfo,
|
||||||
|
$accessInfo->getUserId(),
|
||||||
|
$token,
|
||||||
|
$accessInfo->getScope(),
|
||||||
|
$lifetime
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function validateScopes(AppInfo $appInfo, array $scopes): bool {
|
public function validateScopes(AppInfo $appInfo, array $scopes): bool {
|
||||||
foreach($scopes as $scope)
|
foreach($scopes as $scope)
|
||||||
if(strlen($scope) > 128) // rather than this, actually check if they are defined/supported or smth
|
if(strlen($scope) > 128) // rather than this, actually check if they are defined/supported or smth
|
||||||
|
|
|
@ -7,7 +7,7 @@ class OAuth2RefreshInfo {
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $id,
|
private string $id,
|
||||||
private string $appId,
|
private string $appId,
|
||||||
private string $userId,
|
private ?string $userId,
|
||||||
private ?string $accId,
|
private ?string $accId,
|
||||||
private string $token,
|
private string $token,
|
||||||
private string $scope,
|
private string $scope,
|
||||||
|
@ -19,7 +19,7 @@ class OAuth2RefreshInfo {
|
||||||
return new OAuth2RefreshInfo(
|
return new OAuth2RefreshInfo(
|
||||||
id: $result->getString(0),
|
id: $result->getString(0),
|
||||||
appId: $result->getString(1),
|
appId: $result->getString(1),
|
||||||
userId: $result->getString(2),
|
userId: $result->getStringOrNull(2),
|
||||||
accId: $result->getStringOrNull(3),
|
accId: $result->getStringOrNull(3),
|
||||||
token: $result->getString(4),
|
token: $result->getString(4),
|
||||||
scope: $result->getString(5),
|
scope: $result->getString(5),
|
||||||
|
@ -36,7 +36,10 @@ class OAuth2RefreshInfo {
|
||||||
return $this->appId;
|
return $this->appId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserId(): string {
|
public function hasUserId(): bool {
|
||||||
|
return $this->userId !== null;
|
||||||
|
}
|
||||||
|
public function getUserId(): ?string {
|
||||||
return $this->userId;
|
return $this->userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,17 +48,6 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
#[HttpGet('/oauth2/test')]
|
|
||||||
public function getTest($response) {
|
|
||||||
$response->redirect(self::buildCallbackUri('/oauth2/authorise', [
|
|
||||||
'response_type' => 'code',
|
|
||||||
'client_id' => 'rGKPeDeWQfOVhhHi2qFz',
|
|
||||||
'redirect_uri' => 'https://edgii.net/auth/return',
|
|
||||||
'state' => 'beanz',
|
|
||||||
'scope' => 'windows:xp microsoft:vista windows:forworkgroups',
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[HttpGet('/oauth2/authorise')]
|
#[HttpGet('/oauth2/authorise')]
|
||||||
public function getAuthorise($response, $request) {
|
public function getAuthorise($response, $request) {
|
||||||
$redirectUri = (string)$request->getParam('redirect_uri');
|
$redirectUri = (string)$request->getParam('redirect_uri');
|
||||||
|
@ -71,7 +60,7 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
$clientId = (string)$request->getParam('client_id');
|
$clientId = (string)$request->getParam('client_id');
|
||||||
$appsData = $this->appsCtx->getData();
|
$appsData = $this->appsCtx->getData();
|
||||||
try {
|
try {
|
||||||
$appInfo = $appsData->getApp(clientId: $clientId, deleted: false);
|
$appInfo = $appsData->getAppInfo(clientId: $clientId, deleted: false);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
return $response->redirect(self::buildCallbackUri($redirectUri, [
|
return $response->redirect(self::buildCallbackUri($redirectUri, [
|
||||||
'error' => 'invalid_request',
|
'error' => 'invalid_request',
|
||||||
|
@ -95,7 +84,7 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
$uriInfos = $appsData->getAppUris($appInfo);
|
$uriInfos = $appsData->getAppUriInfos($appInfo);
|
||||||
if(count($uriInfos) !== 1)
|
if(count($uriInfos) !== 1)
|
||||||
return $response->redirect(self::buildCallbackUri($redirectUri, [
|
return $response->redirect(self::buildCallbackUri($redirectUri, [
|
||||||
'error' => 'invalid_request',
|
'error' => 'invalid_request',
|
||||||
|
@ -130,14 +119,7 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
$codeChallengeLength = strlen($codeChallenge);
|
$codeChallengeLength = strlen($codeChallenge);
|
||||||
|
|
||||||
if($codeChallengeMethod === 'plain') {
|
if($codeChallengeMethod === 'plain') {
|
||||||
if($codeChallengeLength === 0) {
|
if($codeChallengeLength < 43)
|
||||||
if($appInfo->isPublic())
|
|
||||||
return $response->redirect(self::buildCallbackUri($redirectUri, [
|
|
||||||
'error' => 'invalid_request',
|
|
||||||
'error_description' => 'Public clients are required to specify a code challenge.',
|
|
||||||
'state' => $state,
|
|
||||||
]));
|
|
||||||
} elseif($codeChallengeLength < 43)
|
|
||||||
return $response->redirect(self::buildCallbackUri($redirectUri, [
|
return $response->redirect(self::buildCallbackUri($redirectUri, [
|
||||||
'error' => 'invalid_request',
|
'error' => 'invalid_request',
|
||||||
'error_description' => 'Code challenge must be at least 43 characters long.',
|
'error_description' => 'Code challenge must be at least 43 characters long.',
|
||||||
|
@ -179,9 +161,9 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
'auth' => $authInfo,
|
'auth' => $authInfo,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$reqsData = $this->oauth2Ctx->getRequestsData();
|
$authoriseData = $this->oauth2Ctx->getAuthoriseData();
|
||||||
try {
|
try {
|
||||||
$requestInfo = $reqsData->createRequest(
|
$authoriseInfo = $authoriseData->createAuthorise(
|
||||||
$appInfo,
|
$appInfo,
|
||||||
$authInfo->user->id,
|
$authInfo->user->id,
|
||||||
$redirectUriId,
|
$redirectUriId,
|
||||||
|
@ -199,7 +181,7 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
|
|
||||||
return $this->templating->render('oauth2/authorise', [
|
return $this->templating->render('oauth2/authorise', [
|
||||||
'app' => $appInfo,
|
'app' => $appInfo,
|
||||||
'req' => $requestInfo,
|
'req' => $authoriseInfo,
|
||||||
'auth' => $authInfo,
|
'auth' => $authInfo,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -224,9 +206,9 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
if(strlen($code) !== 100)
|
if(strlen($code) !== 100)
|
||||||
return 400;
|
return 400;
|
||||||
|
|
||||||
$reqsData = $this->oauth2Ctx->getRequestsData();
|
$authoriseData = $this->oauth2Ctx->getAuthoriseData();
|
||||||
try {
|
try {
|
||||||
$requestInfo = $reqsData->getRequest(
|
$authoriseInfo = $authoriseData->getAuthoriseInfo(
|
||||||
userId: $authInfo->user->id,
|
userId: $authInfo->user->id,
|
||||||
code: $code,
|
code: $code,
|
||||||
);
|
);
|
||||||
|
@ -234,28 +216,28 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$requestInfo->isPending())
|
if(!$authoriseInfo->isPending())
|
||||||
return 410;
|
return 410;
|
||||||
|
|
||||||
$appsData = $this->appsCtx->getData();
|
$appsData = $this->appsCtx->getData();
|
||||||
try {
|
try {
|
||||||
$uriInfo = $appsData->getAppUri($requestInfo->getUriId());
|
$uriInfo = $appsData->getAppUriInfo($authoriseInfo->getUriId());
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
return 400;
|
return 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
$approved = $approve === 'yes';
|
$approved = $approve === 'yes';
|
||||||
$reqsData->setRequestApproval($requestInfo, $approved);
|
$authoriseData->setAuthoriseApproval($authoriseInfo, $approved);
|
||||||
|
|
||||||
if($approved)
|
if($approved)
|
||||||
$response->redirect(self::buildCallbackUri($uriInfo->getString(), [
|
$response->redirect(self::buildCallbackUri($uriInfo->getString(), [
|
||||||
'code' => $requestInfo->getCode(),
|
'code' => $authoriseInfo->getCode(),
|
||||||
'state' => $requestInfo->getState(),
|
'state' => $authoriseInfo->getState(),
|
||||||
]));
|
]));
|
||||||
else
|
else
|
||||||
$response->redirect(self::buildCallbackUri($uriInfo->getString(), [
|
$response->redirect(self::buildCallbackUri($uriInfo->getString(), [
|
||||||
'error' => 'access_denied',
|
'error' => 'access_denied',
|
||||||
'state' => $requestInfo->getState(),
|
'state' => $authoriseInfo->getState(),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,98 +338,142 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
$appsData = $this->appsCtx->getData();
|
$appsData = $this->appsCtx->getData();
|
||||||
$reqsData = $this->oauth2Ctx->getRequestsData();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$appInfo = $appsData->getApp(clientId: $clientId, deleted: false);
|
$appInfo = $appsData->getAppInfo(clientId: $clientId, deleted: false);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
return self::error('invalid_client', 'No application has been registered with this client id.');
|
return self::error('invalid_client', 'No application has been registered with this client id.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = (string)$content->getParam('grant_type');
|
$appAuthenticated = false;
|
||||||
if($type === 'authorization_code') {
|
if($clientSecret !== '') {
|
||||||
// require a code verifier be used if client_secret is not supplied or if the field for it is populated
|
// TODO: rate limiting
|
||||||
// error code for this is: invalid_request
|
$appAuthenticated = $appInfo->verifyClientSecret($clientSecret);
|
||||||
$codeVerifier = (string)$content->getParam('code_verifier');
|
if(!$appAuthenticated) {
|
||||||
$hasCodeVerifier = $codeVerifier !== '';
|
|
||||||
if($clientSecret === '') {
|
|
||||||
if(!$hasCodeVerifier) {
|
|
||||||
$response->setStatusCode(400);
|
|
||||||
return self::error('invalid_request', 'Application authentication through client secret is required if no code verifier is specified.');
|
|
||||||
}
|
|
||||||
} elseif(!$appInfo->verifyClientSecret($clientSecret)) {
|
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
return self::error('invalid_client', 'Provided client secret is not correct for this application.');
|
return self::error('invalid_client', 'Provided client secret is not correct for this application.');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = (string)$content->getParam('grant_type');
|
||||||
|
if($type === 'authorization_code') {
|
||||||
|
$authoriseData = $this->oauth2Ctx->getAuthoriseData();
|
||||||
try {
|
try {
|
||||||
$requestInfo = $reqsData->getRequest(code: (string)$content->getParam('code'));
|
$authoriseInfo = $authoriseData->getAuthoriseInfo(
|
||||||
|
appInfo: $appInfo,
|
||||||
|
code: (string)$content->getParam('code'),
|
||||||
|
);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
return self::error('invalid_grant', 'No authorisation request with this code exists.');
|
return self::error('invalid_grant', 'No authorisation request with this code exists.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if($requestInfo->hasExpired()) {
|
if($authoriseInfo->hasExpired()) {
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
return self::error('invalid_grant', 'Authorisation request has expired.');
|
return self::error('invalid_grant', 'Authorisation request has expired.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if($requestInfo->hasCodeChallenge()) {
|
if(!$authoriseInfo->verifyCodeChallenge((string)$content->getParam('code_verifier'))) {
|
||||||
if(!$hasCodeVerifier) {
|
|
||||||
$response->setStatusCode(400);
|
|
||||||
return self::error('invalid_request', 'Authorisation required included a code challenge, but no code verifier is supplied.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!$requestInfo->verifyCodeChallenge($codeVerifier)) {
|
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
return self::error('invalid_request', 'Code challenge verification failed.');
|
return self::error('invalid_request', 'Code challenge verification failed.');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$redirectUri = (string)$content->getParam('redirect_uri');
|
if(!$authoriseInfo->isApproved()) {
|
||||||
if($redirectUri === '') {
|
|
||||||
if(!$hasCodeVerifier && $appInfo->isPublic()) {
|
|
||||||
$response->setStatusCode(400);
|
|
||||||
return self::error('invalid_request', 'You must specified the redirect URI if no code verifier is specified.');
|
|
||||||
}
|
|
||||||
} elseif($appsData->getAppUriId($appInfo, $redirectUri) !== $requestInfo->getUriId()) {
|
|
||||||
$response->setStatusCode(400);
|
|
||||||
return self::error('invalid_request', 'Provided redirect URI does not match up with the one used during the authorisation request.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!$requestInfo->isApproved()) {
|
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
return self::error('invalid_grant', 'Authorisation request has not been approved.');
|
return self::error('invalid_grant', 'Authorisation request has not been approved.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$scopes = $requestInfo->getScopes();
|
// its entirely verified!
|
||||||
|
$authoriseData->deleteAuthorise($authoriseInfo);
|
||||||
|
|
||||||
|
$scopes = $authoriseInfo->getScopes();
|
||||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes)) {
|
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes)) {
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||||
}
|
}
|
||||||
|
$scope = implode(' ', $scopes);
|
||||||
|
|
||||||
|
$tokensData = $this->oauth2Ctx->getTokensData();
|
||||||
|
$accessInfo = $tokensData->createAccess(
|
||||||
|
$appInfo,
|
||||||
|
$authoriseInfo->getUserId(),
|
||||||
|
scope: $scope,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 'scope' only has to be in the response if it differs from what was requested
|
||||||
|
if($scope === $authoriseInfo->getScope())
|
||||||
|
unset($scope);
|
||||||
|
|
||||||
|
// this should probably check something else
|
||||||
|
if($appInfo->isConfidential())
|
||||||
|
$refreshInfo = $this->oauth2Ctx->createRefreshFromAccessInfo($accessInfo);
|
||||||
} elseif($type === 'refresh_token') {
|
} elseif($type === 'refresh_token') {
|
||||||
$refreshToken = (string)$content->getParam('refresh_token');
|
$tokensData = $this->oauth2Ctx->getTokensData();
|
||||||
|
try {
|
||||||
|
$refreshInfo = $tokensData->getRefreshInfo((string)$content->getParam('refresh_token'), OAuth2TokensData::REFRESH_BY_TOKEN);
|
||||||
|
} catch(RuntimeException $ex) {
|
||||||
|
$response->setStatusCode(400);
|
||||||
|
return self::error('invalid_grant', 'No such refresh token exists.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($refreshInfo->getAppId() !== $appInfo->getId()) {
|
||||||
|
$response->setStatusCode(400);
|
||||||
|
return self::error('invalid_grant', 'This refresh token is not associated with this application.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($refreshInfo->hasExpired()) {
|
||||||
|
$response->setStatusCode(400);
|
||||||
|
return self::error('invalid_grant', 'This refresh token has expired.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokensData->deleteRefresh($refreshInfo);
|
||||||
|
|
||||||
|
if($refreshInfo->hasAccessId())
|
||||||
|
$tokensData->deleteAccess(accessInfo: $refreshInfo->getAccessId());
|
||||||
|
|
||||||
// should not contain more than the original access_token, refresh info contains the stuff we need!
|
// should not contain more than the original access_token, refresh info contains the stuff we need!
|
||||||
$newScopes = self::filterScopes((string)$content->getParam('scope'));
|
$newScopes = self::filterScopes((string)$content->getParam('scope'));
|
||||||
$oldScopes = [];
|
$oldScopes = [];
|
||||||
|
|
||||||
if($this->oauth2Ctx->validateScopes($appInfo, $newScopes)) {
|
if(!$this->oauth2Ctx->validateScopes($appInfo, $newScopes)) {
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$accessInfo = $tokensData->createAccess(
|
||||||
|
$appInfo,
|
||||||
|
$refreshInfo->getUserId(),
|
||||||
|
scope: $refreshInfo->getScope(), // just copy from refreshInfo for now, should reverify!!
|
||||||
|
);
|
||||||
|
|
||||||
|
unset($refreshInfo);
|
||||||
|
|
||||||
|
// this should probably check something else
|
||||||
|
if($appInfo->isConfidential())
|
||||||
|
$refreshInfo = $this->oauth2Ctx->createRefreshFromAccessInfo($accessInfo);
|
||||||
} elseif($type === 'client_credentials') {
|
} elseif($type === 'client_credentials') {
|
||||||
// uses client_secret
|
if(!$appInfo->isConfidential()) {
|
||||||
} elseif($type === 'password') {
|
|
||||||
// still really not sure if i should bother with implementing this, especially since its omitted from OAuth 2.1 entirely
|
|
||||||
if(!$appInfo->isTrusted()) {
|
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
return self::error('unauthorized_client', 'This application is not allowed to use this grant type.');
|
return self::error('unauthorized_client', 'This application is not allowed to use this grant type.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$userName = (string)$content->getParam('username');
|
if(!$appAuthenticated) {
|
||||||
$password = (string)$content->getParam('password');
|
$response->setStatusCode(400);
|
||||||
|
return self::error('invalid_client', 'Application must authenticate with client secret in order to use this grant type.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$scopes = self::filterScopes((string)$content->getParam('scope'));
|
||||||
|
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes)) {
|
||||||
|
$response->setStatusCode(400);
|
||||||
|
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||||
|
}
|
||||||
|
$scope = implode(' ', $scopes);
|
||||||
|
|
||||||
|
$accessInfo = $this->oauth2Ctx->getTokensData()->createAccess($appInfo, scope: $scope);
|
||||||
|
|
||||||
|
// i'll just unset it, really need to dive deeper into the scope sitch
|
||||||
|
unset($scope);
|
||||||
} elseif($type === 'device_code' || $type === 'urn:ietf:params:oauth:grant-type:device_code') {
|
} elseif($type === 'device_code' || $type === 'urn:ietf:params:oauth:grant-type:device_code') {
|
||||||
$deviceCode = (string)$content->getParam('device_code');
|
$deviceCode = (string)$content->getParam('device_code');
|
||||||
|
|
||||||
|
@ -477,29 +503,22 @@ final class OAuth2Routes extends RouteHandler {
|
||||||
|
|
||||||
if(empty($accessInfo)) {
|
if(empty($accessInfo)) {
|
||||||
$response->setStatusCode(400);
|
$response->setStatusCode(400);
|
||||||
|
return self::error('invalid_grant', 'Failed to request access token.');
|
||||||
if($type === 'refresh_token')
|
|
||||||
$message = 'Failed to request new access token. Provided refresh token has likely expired or is invalid.';
|
|
||||||
elseif($type === 'password')
|
|
||||||
$message = 'Failed to request access token, user name or password was incorrect.';
|
|
||||||
elseif($type === 'client_credentials')
|
|
||||||
$message = 'Failed to request application access token.';
|
|
||||||
else
|
|
||||||
$message = 'Failed to request access token.';
|
|
||||||
|
|
||||||
return self::error('invalid_grant', $message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
'access_token' => $accessInfo->getToken(),
|
'access_token' => $accessInfo->getToken(),
|
||||||
'token_type' => 'Bearer',
|
'token_type' => 'Bearer',
|
||||||
'expires_in' => $accessInfo->getRemainingLifetime(),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if(isset($scopes))
|
$expiresIn = $accessInfo->getRemainingLifetime();
|
||||||
$result['scope'] = implode(' ', $scopes);
|
if($expiresIn < 3600)
|
||||||
|
$result['expires_in'] = $expiresIn;
|
||||||
|
|
||||||
if(empty($refreshInfo))
|
if(isset($scope))
|
||||||
|
$result['scope'] = $scope;
|
||||||
|
|
||||||
|
if(!empty($refreshInfo))
|
||||||
$result['refresh_token'] = $refreshInfo->getToken();
|
$result['refresh_token'] = $refreshInfo->getToken();
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
|
|
@ -6,6 +6,7 @@ use RuntimeException;
|
||||||
use Index\XString;
|
use Index\XString;
|
||||||
use Index\Data\DbStatementCache;
|
use Index\Data\DbStatementCache;
|
||||||
use Index\Data\IDbConnection;
|
use Index\Data\IDbConnection;
|
||||||
|
use Hanyuu\Apps\AppInfo;
|
||||||
|
|
||||||
class OAuth2TokensData {
|
class OAuth2TokensData {
|
||||||
private IDbConnection $dbConn;
|
private IDbConnection $dbConn;
|
||||||
|
@ -16,4 +17,172 @@ class OAuth2TokensData {
|
||||||
$this->cache = new DbStatementCache($dbConn);
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const ACCESS_BY_ID = 'id';
|
||||||
|
public const ACCESS_BY_TOKEN = 'token';
|
||||||
|
|
||||||
|
public function getAccessInfo(string $value, string $select): OAuth2AccessInfo {
|
||||||
|
if($select === self::ACCESS_BY_ID)
|
||||||
|
$select = 'acc_id';
|
||||||
|
elseif($select === self::ACCESS_BY_TOKEN)
|
||||||
|
$select = 'acc_token';
|
||||||
|
else
|
||||||
|
throw new InvalidArgumentException('$select is not a valid select mode');
|
||||||
|
|
||||||
|
$stmt = $this->cache->get(sprintf(
|
||||||
|
'SELECT acc_id, app_id, user_id, acc_token, acc_scope, UNIX_TIMESTAMP(acc_created), UNIX_TIMESTAMP(acc_expires) FROM hau_oauth2_access WHERE %s = ?',
|
||||||
|
$select
|
||||||
|
));
|
||||||
|
$stmt->addParameter(1, $value);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
if(!$result->next())
|
||||||
|
throw new RuntimeException('Access info not found.');
|
||||||
|
|
||||||
|
return OAuth2AccessInfo::fromResult($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createAccess(
|
||||||
|
AppInfo|string $appInfo,
|
||||||
|
?string $userId = null,
|
||||||
|
?string $token = null,
|
||||||
|
string $scope = '',
|
||||||
|
?int $lifetime = null
|
||||||
|
): OAuth2AccessInfo {
|
||||||
|
$token ??= XString::random(80);
|
||||||
|
|
||||||
|
$stmt = $this->cache->get(sprintf('INSERT INTO hau_oauth2_access (app_id, user_id, acc_token, acc_scope, acc_expires) VALUES (?, ?, ?, ?, IF(?, NOW() + INTERVAL ? SECOND, DEFAULT(acc_expires)))'));
|
||||||
|
$stmt->addParameter(1, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo);
|
||||||
|
$stmt->addParameter(2, $userId);
|
||||||
|
$stmt->addParameter(3, $token);
|
||||||
|
$stmt->addParameter(4, $scope);
|
||||||
|
$stmt->addParameter(5, $lifetime === null ? 0 : 1);
|
||||||
|
$stmt->addParameter(6, $lifetime);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $this->getAccessInfo((string)$this->dbConn->getLastInsertId(), self::ACCESS_BY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteAccess(
|
||||||
|
OAuth2AccessInfo|string|null $accessInfo = null,
|
||||||
|
AppInfo|string|null $appInfo = null,
|
||||||
|
?string $userId = null,
|
||||||
|
): void {
|
||||||
|
$selectors = [];
|
||||||
|
$values = [];
|
||||||
|
if($accessInfo !== null) {
|
||||||
|
$selectors[] = 'acc_id = ?';
|
||||||
|
$values[] = $accessInfo instanceof OAuth2AccessInfo ? $accessInfo->getId() : $accessInfo;
|
||||||
|
}
|
||||||
|
if($appInfo !== null) {
|
||||||
|
$selectors[] = 'app_id = ?';
|
||||||
|
$values[] = $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo;
|
||||||
|
}
|
||||||
|
if($userId !== null) {
|
||||||
|
$selectors[] = 'user_id = ?';
|
||||||
|
$values[] = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = 'DELETE FROM hau_oauth2_access';
|
||||||
|
if(!empty($selectors))
|
||||||
|
$query .= sprintf(' WHERE %s', implode(' AND ', $selectors));
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$stmt = $this->cache->get($query);
|
||||||
|
foreach($values as $value)
|
||||||
|
$stmt->addParameter(++$args, $value);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public const REFRESH_BY_ID = 'id';
|
||||||
|
public const REFRESH_BY_ACCESS = 'access';
|
||||||
|
public const REFRESH_BY_TOKEN = 'token';
|
||||||
|
|
||||||
|
public function getRefreshInfo(object|string $value, string $select): OAuth2RefreshInfo {
|
||||||
|
if($select === self::REFRESH_BY_ID) {
|
||||||
|
$select = 'ref_id';
|
||||||
|
} elseif($select === self::REFRESH_BY_ACCESS) {
|
||||||
|
$select = 'acc_id';
|
||||||
|
if($value instanceof OAuth2AccessInfo)
|
||||||
|
$value = $value->getId();
|
||||||
|
} elseif($select === self::REFRESH_BY_TOKEN) {
|
||||||
|
$select = 'ref_token';
|
||||||
|
} else
|
||||||
|
throw new InvalidArgumentException('$select is not a valid select mode');
|
||||||
|
|
||||||
|
if(!is_string($value))
|
||||||
|
throw new InvalidArgumentException('$value must be a string');
|
||||||
|
|
||||||
|
$stmt = $this->cache->get(sprintf(
|
||||||
|
'SELECT ref_id, app_id, user_id, acc_id, ref_token, ref_scope, UNIX_TIMESTAMP(ref_created), UNIX_TIMESTAMP(ref_expires) FROM hau_oauth2_refresh WHERE %s = ?',
|
||||||
|
$select
|
||||||
|
));
|
||||||
|
$stmt->addParameter(1, $value);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
if(!$result->next())
|
||||||
|
throw new RuntimeException('Refresh info not found.');
|
||||||
|
|
||||||
|
return OAuth2RefreshInfo::fromResult($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createRefresh(
|
||||||
|
AppInfo|string $appInfo,
|
||||||
|
OAuth2AccessInfo|string|null $accessInfo,
|
||||||
|
?string $userId = null,
|
||||||
|
?string $token = null,
|
||||||
|
string $scope = '',
|
||||||
|
?int $lifetime = null
|
||||||
|
): OAuth2RefreshInfo {
|
||||||
|
$token ??= XString::random(120);
|
||||||
|
|
||||||
|
$stmt = $this->cache->get(sprintf('INSERT INTO hau_oauth2_refresh (app_id, user_id, acc_id, ref_token, ref_scope, ref_expires) VALUES (?, ?, ?, ?, ?, IF(?, NOW() + INTERVAL ? SECOND, DEFAULT(ref_expires)))'));
|
||||||
|
$stmt->addParameter(1, $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo);
|
||||||
|
$stmt->addParameter(2, $userId);
|
||||||
|
$stmt->addParameter(3, $accessInfo instanceof OAuth2AccessInfo ? $accessInfo->getId() : $accessInfo);
|
||||||
|
$stmt->addParameter(4, $token);
|
||||||
|
$stmt->addParameter(5, $scope);
|
||||||
|
$stmt->addParameter(6, $lifetime === null ? 0 : 1);
|
||||||
|
$stmt->addParameter(7, $lifetime);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $this->getRefreshInfo((string)$this->dbConn->getLastInsertId(), self::REFRESH_BY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteRefresh(
|
||||||
|
OAuth2RefreshInfo|string|null $refreshInfo = null,
|
||||||
|
AppInfo|string|null $appInfo = null,
|
||||||
|
?string $userId = null,
|
||||||
|
OAuth2AccessInfo|string|null $accessInfo = null
|
||||||
|
): void {
|
||||||
|
$selectors = [];
|
||||||
|
$values = [];
|
||||||
|
if($refreshInfo !== null) {
|
||||||
|
$selectors[] = 'ref_id = ?';
|
||||||
|
$values[] = $refreshInfo instanceof OAuth2RefreshInfo ? $refreshInfo->getId() : $refreshInfo;
|
||||||
|
}
|
||||||
|
if($appInfo !== null) {
|
||||||
|
$selectors[] = 'app_id = ?';
|
||||||
|
$values[] = $appInfo instanceof AppInfo ? $appInfo->getId() : $appInfo;
|
||||||
|
}
|
||||||
|
if($userId !== null) {
|
||||||
|
$selectors[] = 'user_id = ?';
|
||||||
|
$values[] = $userId;
|
||||||
|
}
|
||||||
|
if($accessInfo !== null) {
|
||||||
|
$selectors[] = 'acc_id = ?';
|
||||||
|
$values[] = $accessInfo instanceof OAuth2AccessInfo ? $accessInfo->getId() : $accessInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = 'DELETE FROM hau_oauth2_refresh';
|
||||||
|
if(!empty($selectors))
|
||||||
|
$query .= sprintf(' WHERE %s', implode(' AND ', $selectors));
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$stmt = $this->cache->get($query);
|
||||||
|
foreach($values as $value)
|
||||||
|
$stmt->addParameter(++$args, $value);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue