OAuth metadata endpoints.

This commit is contained in:
flash 2025-02-26 18:06:46 +00:00
parent e5a947e973
commit b2f8347526
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
4 changed files with 99 additions and 2 deletions

View file

@ -0,0 +1,12 @@
<?php
use Index\Db\DbConnection;
use Index\Db\Migration\DbMigration;
final class AddIndexOnRestrictedFieldForScopes_20250226_173829 implements DbMigration {
public function migrate(DbConnection $conn): void {
$conn->execute(<<<SQL
ALTER TABLE msz_scopes
ADD INDEX scopes_restricted_index (scope_restricted);
SQL);
}
}

View file

@ -12,6 +12,29 @@ class ScopesData {
$this->cache = new DbStatementCache($dbConn); $this->cache = new DbStatementCache($dbConn);
} }
public function getScopes(
?bool $restricted = null,
?bool $deprecated = null
): iterable {
$args = 0;
$query = <<<SQL
SELECT scope_id, scope_string, scope_restricted, scope_summary,
UNIX_TIMESTAMP(scope_created), UNIX_TIMESTAMP(scope_deprecated)
FROM msz_scopes
SQL;
if($restricted !== null) {
++$args;
$query .= sprintf(' WHERE scope_restricted %s 0', $restricted ? '<>' : '=');
}
if($deprecated !== null)
$query .= sprintf(' %s scope_deprecated %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deprecated ? 'IS NOT' : 'IS');
$stmt = $this->cache->get($query);
$stmt->execute();
return $stmt->getResultIterator(ScopeInfo::fromResult(...));
}
public function getScopeInfo(string $value, ScopeInfoGetField $field): ScopeInfo { public function getScopeInfo(string $value, ScopeInfoGetField $field): ScopeInfo {
$stmt = $this->cache->get(sprintf( $stmt = $this->cache->get(sprintf(
<<<SQL <<<SQL

View file

@ -2,15 +2,21 @@
namespace Misuzu\OAuth2; namespace Misuzu\OAuth2;
use RuntimeException; use RuntimeException;
use Index\XArray;
use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest}; use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpGet,HttpOptions,HttpPost,RouteHandler,RouteHandlerCommon}; use Index\Http\Routing\{HttpGet,HttpOptions,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Urls\{UrlFormat,UrlSource,UrlSourceCommon}; use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\SiteInfo;
use Misuzu\Apps\AppsContext;
final class OAuth2ApiRoutes implements RouteHandler, UrlSource { final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
use RouteHandlerCommon, UrlSourceCommon; use RouteHandlerCommon, UrlSourceCommon;
public function __construct( public function __construct(
private OAuth2Context $oauth2Ctx, private OAuth2Context $oauth2Ctx,
private AppsContext $appsCtx,
private UrlRegistry $urls,
private SiteInfo $siteInfo,
) {} ) {}
/** /**
@ -38,6 +44,62 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
return $result; return $result;
} }
#[HttpOptions('/.well-known/oauth-protected-resource')]
#[HttpGet('/.well-known/oauth-protected-resource')]
public function getWellKnownProtectedResource(HttpResponseBuilder $response, HttpRequest $request): array|int {
$response->setHeader('Access-Control-Allow-Origin', '*');
if($request->method === 'OPTIONS')
return 204;
return [
'resource' => $this->siteInfo->url,
'authorization_servers' => [$this->siteInfo->url],
'scopes_supported' => [],
'bearer_methods_supported' => ['header'],
];
}
#[HttpOptions('/.well-known/oauth-authorization-server')]
#[HttpGet('/.well-known/oauth-authorization-server')]
public function getWellKnownAuthorizationServer(HttpResponseBuilder $response, HttpRequest $request): array|int {
$response->setHeader('Access-Control-Allow-Origin', '*');
if($request->method === 'OPTIONS')
return 204;
return [
'issuer' => $this->siteInfo->url,
'authorization_endpoint' => sprintf('%s%s', $this->siteInfo->url, $this->urls->format('oauth2-authorise')),
'token_endpoint' => sprintf('%s%s', $this->siteInfo->url, $this->urls->format('oauth2-token')),
'jwks_uri' => sprintf('%s%s', $this->siteInfo->url, $this->urls->format('openid-jwks')),
'protected_resources' => [$this->siteInfo->url],
'scopes_supported' => XArray::select(
$this->appsCtx->scopes->getScopes(
restricted: false,
deprecated: false,
),
fn($scopeInfo) => $scopeInfo->string
),
'response_types_supported' => ['code', 'code id_token'],
'response_modes_supported' => ['query'],
'grant_types_supported' => [
'authorization_code',
'client_credentials',
'refresh_token',
'urn:ietf:params:oauth:grant-type:device_code',
],
'token_endpoint_auth_methods_supported' => [
'none',
'client_secret_basic',
'client_secret_post',
],
//'revocation_endpoint' => 'TODO: implement this',
//'revocation_endpoint_auth_methods_supported' => ['client_secret_basic'],
//'introspection_endpoint ' => 'TODO: implement this',
//'introspection_endpoint_auth_methods_supported' => ['Bearer'],
'code_challenge_methods_supported' => ['plain', 'S256'],
];
}
/** /**
* @return array{ * @return array{
* device_code: string, * device_code: string,

View file

@ -23,7 +23,7 @@ class OpenIDRoutes implements RouteHandler, UrlSource {
private ProfileContext $profileCtx, private ProfileContext $profileCtx,
private SiteInfo $siteInfo, private SiteInfo $siteInfo,
private AuthInfo $authInfo, private AuthInfo $authInfo,
private UrlRegistry $urls private UrlRegistry $urls,
) {} ) {}
#[HttpOptions('/.well-known/openid-configuration')] #[HttpOptions('/.well-known/openid-configuration')]