PHPStan fixes.
This commit is contained in:
parent
446c675613
commit
17251cf750
19 changed files with 326 additions and 66 deletions
11
composer.lock
generated
11
composer.lock
generated
|
@ -1041,16 +1041,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "matomo/device-detector",
|
"name": "matomo/device-detector",
|
||||||
"version": "6.4.3",
|
"version": "6.4.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/matomo-org/device-detector.git",
|
"url": "https://github.com/matomo-org/device-detector.git",
|
||||||
"reference": "aa4586d495a7f59029d46d976f160b13eb769bb0"
|
"reference": "270bbc41f80994e80805ac377b67324eba53c412"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/aa4586d495a7f59029d46d976f160b13eb769bb0",
|
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/270bbc41f80994e80805ac377b67324eba53c412",
|
||||||
"reference": "aa4586d495a7f59029d46d976f160b13eb769bb0",
|
"reference": "270bbc41f80994e80805ac377b67324eba53c412",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1067,6 +1067,7 @@
|
||||||
"phpunit/phpunit": "^8.5.8",
|
"phpunit/phpunit": "^8.5.8",
|
||||||
"psr/cache": "^1.0.1",
|
"psr/cache": "^1.0.1",
|
||||||
"psr/simple-cache": "^1.0.1",
|
"psr/simple-cache": "^1.0.1",
|
||||||
|
"slevomat/coding-standard": "<8.16.0",
|
||||||
"symfony/yaml": "^5.1.7"
|
"symfony/yaml": "^5.1.7"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
|
@ -1106,7 +1107,7 @@
|
||||||
"source": "https://github.com/matomo-org/matomo",
|
"source": "https://github.com/matomo-org/matomo",
|
||||||
"wiki": "https://dev.matomo.org/"
|
"wiki": "https://dev.matomo.org/"
|
||||||
},
|
},
|
||||||
"time": "2025-01-17T09:59:39+00:00"
|
"time": "2025-02-26T17:37:32+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mustangostang/spyc",
|
"name": "mustangostang/spyc",
|
||||||
|
|
|
@ -12,6 +12,9 @@ class ScopesData {
|
||||||
$this->cache = new DbStatementCache($dbConn);
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<ScopeInfo>
|
||||||
|
*/
|
||||||
public function getScopes(
|
public function getScopes(
|
||||||
?bool $restricted = null,
|
?bool $restricted = null,
|
||||||
?bool $deprecated = null
|
?bool $deprecated = null
|
||||||
|
|
|
@ -8,7 +8,6 @@ use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
|
||||||
use Index\Syndication\FeedBuilder;
|
use Index\Syndication\FeedBuilder;
|
||||||
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
|
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
|
||||||
use Misuzu\{Pagination,SiteInfo,Template};
|
use Misuzu\{Pagination,SiteInfo,Template};
|
||||||
use Misuzu\Auth\AuthInfo;
|
|
||||||
use Misuzu\Comments\CommentsContext;
|
use Misuzu\Comments\CommentsContext;
|
||||||
use Misuzu\Users\UsersContext;
|
use Misuzu\Users\UsersContext;
|
||||||
|
|
||||||
|
@ -21,7 +20,6 @@ final class ChangelogRoutes implements RouteHandler, UrlSource {
|
||||||
private ChangelogData $changelog,
|
private ChangelogData $changelog,
|
||||||
private UsersContext $usersCtx,
|
private UsersContext $usersCtx,
|
||||||
private CommentsContext $commentsCtx,
|
private CommentsContext $commentsCtx,
|
||||||
private AuthInfo $authInfo,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[HttpGet('/changelog')]
|
#[HttpGet('/changelog')]
|
||||||
|
|
|
@ -27,6 +27,15 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
: new PermissionResult(0);
|
: new PermissionResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{
|
||||||
|
* id: string,
|
||||||
|
* name: string,
|
||||||
|
* profile: string,
|
||||||
|
* avatar: string,
|
||||||
|
* colour?: string,
|
||||||
|
* }
|
||||||
|
*/
|
||||||
private function convertUser(UserInfo $userInfo, int $avatarRes = 80): array {
|
private function convertUser(UserInfo $userInfo, int $avatarRes = 80): array {
|
||||||
$user = [
|
$user = [
|
||||||
'id' => $userInfo->id,
|
'id' => $userInfo->id,
|
||||||
|
@ -42,6 +51,10 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param iterable<CommentsPostInfo> $postInfos
|
||||||
|
* @return mixed[]
|
||||||
|
*/
|
||||||
private function convertPosts(
|
private function convertPosts(
|
||||||
IPermissionResult $perms,
|
IPermissionResult $perms,
|
||||||
CommentsCategoryInfo $catInfo,
|
CommentsCategoryInfo $catInfo,
|
||||||
|
@ -66,6 +79,31 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return $posts;
|
return $posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ?iterable<CommentsPostInfo> $replyInfos
|
||||||
|
* @return array{
|
||||||
|
* id: string,
|
||||||
|
* body?: string,
|
||||||
|
* created: string,
|
||||||
|
* pinned?: string,
|
||||||
|
* edited?: string,
|
||||||
|
* deleted?: string|true,
|
||||||
|
* user?: array{
|
||||||
|
* id: string,
|
||||||
|
* name: string,
|
||||||
|
* profile: string,
|
||||||
|
* avatar: string,
|
||||||
|
* colour?: string,
|
||||||
|
* },
|
||||||
|
* positive?: int,
|
||||||
|
* negative?: int,
|
||||||
|
* vote?: int,
|
||||||
|
* can_edit?: true,
|
||||||
|
* can_delete?: true,
|
||||||
|
* can_delete_any?: true,
|
||||||
|
* replies?: int|mixed[],
|
||||||
|
* }
|
||||||
|
*/
|
||||||
private function convertPost(
|
private function convertPost(
|
||||||
IPermissionResult $perms,
|
IPermissionResult $perms,
|
||||||
CommentsCategoryInfo $catInfo,
|
CommentsCategoryInfo $catInfo,
|
||||||
|
@ -86,7 +124,7 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
if($postInfo->edited)
|
if($postInfo->edited)
|
||||||
$post['edited'] = $postInfo->editedAt->toIso8601ZuluString();
|
$post['edited'] = $postInfo->editedAt->toIso8601ZuluString();
|
||||||
|
|
||||||
if(!$isDeleted && $postInfo->userId !== null)
|
if($postInfo->userId !== null)
|
||||||
try {
|
try {
|
||||||
$post['user'] = $this->convertUser(
|
$post['user'] = $this->convertUser(
|
||||||
$this->usersCtx->getUserInfo($postInfo->userId)
|
$this->usersCtx->getUserInfo($postInfo->userId)
|
||||||
|
@ -132,6 +170,10 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return $post;
|
return $post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $extra
|
||||||
|
* @return array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
private static function error(HttpResponseBuilder $response, int $code, string $name, string $text, array $extra = []): array {
|
private static function error(HttpResponseBuilder $response, int $code, string $name, string $text, array $extra = []): array {
|
||||||
$response->statusCode = $code;
|
$response->statusCode = $code;
|
||||||
|
|
||||||
|
@ -143,7 +185,7 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return void|int|array{error: array{name: string, text: string}} */
|
/** @return void|array{error: array{name: string, text: string}} */
|
||||||
#[HttpMiddleware('/comments')]
|
#[HttpMiddleware('/comments')]
|
||||||
public function checkCsrf(HttpResponseBuilder $response, HttpRequest $request) {
|
public function checkCsrf(HttpResponseBuilder $response, HttpRequest $request) {
|
||||||
if(in_array($request->method, ['DELETE', 'PATCH', 'POST'])) {
|
if(in_array($request->method, ['DELETE', 'PATCH', 'POST'])) {
|
||||||
|
@ -158,6 +200,34 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{
|
||||||
|
* category: array{
|
||||||
|
* name: string,
|
||||||
|
* created: string,
|
||||||
|
* locked?: string,
|
||||||
|
* owner?: array{
|
||||||
|
* id: string,
|
||||||
|
* name: string,
|
||||||
|
* profile: string,
|
||||||
|
* avatar: string,
|
||||||
|
* colour?: string,
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* user?: array{
|
||||||
|
* id: string,
|
||||||
|
* name: string,
|
||||||
|
* profile: string,
|
||||||
|
* avatar: string,
|
||||||
|
* colour?: string,
|
||||||
|
* can_create?: true,
|
||||||
|
* can_pin?: true,
|
||||||
|
* can_vote?: true,
|
||||||
|
* can_lock?: true,
|
||||||
|
* },
|
||||||
|
* posts: mixed[]
|
||||||
|
* }|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpGet('/comments/categories/([A-Za-z0-9-]+)')]
|
#[HttpGet('/comments/categories/([A-Za-z0-9-]+)')]
|
||||||
public function getCategory(HttpResponseBuilder $response, HttpRequest $request, string $categoryName): array {
|
public function getCategory(HttpResponseBuilder $response, HttpRequest $request, string $categoryName): array {
|
||||||
try {
|
try {
|
||||||
|
@ -214,6 +284,12 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{
|
||||||
|
* name: string,
|
||||||
|
* locked?: string|false,
|
||||||
|
* }|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpPost('/comments/categories/([A-Za-z0-9-]+)')]
|
#[HttpPost('/comments/categories/([A-Za-z0-9-]+)')]
|
||||||
public function patchCategory(HttpResponseBuilder $response, HttpRequest $request, string $categoryName): array {
|
public function patchCategory(HttpResponseBuilder $response, HttpRequest $request, string $categoryName): array {
|
||||||
if(!($request->content instanceof FormHttpContent))
|
if(!($request->content instanceof FormHttpContent))
|
||||||
|
@ -253,6 +329,9 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[]|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpPost('/comments/posts')]
|
#[HttpPost('/comments/posts')]
|
||||||
public function postPost(HttpResponseBuilder $response, HttpRequest $request): array {
|
public function postPost(HttpResponseBuilder $response, HttpRequest $request): array {
|
||||||
if(!($request->content instanceof FormHttpContent))
|
if(!($request->content instanceof FormHttpContent))
|
||||||
|
@ -314,6 +393,9 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return $this->convertPost($perms, $catInfo, $postInfo);
|
return $this->convertPost($perms, $catInfo, $postInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[]|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpGet('/comments/posts/([0-9]+)')]
|
#[HttpGet('/comments/posts/([0-9]+)')]
|
||||||
public function getPost(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
public function getPost(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
||||||
try {
|
try {
|
||||||
|
@ -336,6 +418,9 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return $post;
|
return $post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[]|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpGet('/comments/posts/([0-9]+)/replies')]
|
#[HttpGet('/comments/posts/([0-9]+)/replies')]
|
||||||
public function getPostReplies(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
public function getPostReplies(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
||||||
try {
|
try {
|
||||||
|
@ -352,9 +437,17 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{
|
||||||
|
* id: string,
|
||||||
|
* body?: string,
|
||||||
|
* pinned?: string|false,
|
||||||
|
* edited?: string,
|
||||||
|
* }|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
|
#[HttpPost('/comments/posts/([0-9]+)')]
|
||||||
// this should be HttpPatch but PHP doesn't parse into $_POST for PATCH...
|
// this should be HttpPatch but PHP doesn't parse into $_POST for PATCH...
|
||||||
// fix this in the v3 router for index by just ignoring PHP's parsing altogether
|
// fix this in the v3 router for index by just ignoring PHP's parsing altogether
|
||||||
#[HttpPost('/comments/posts/([0-9]+)')]
|
|
||||||
public function patchPost(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
public function patchPost(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
||||||
if(!($request->content instanceof FormHttpContent))
|
if(!($request->content instanceof FormHttpContent))
|
||||||
return self::error($response, 400, 'comments:content', 'Provided content could not be understood.');
|
return self::error($response, 400, 'comments:content', 'Provided content could not be understood.');
|
||||||
|
@ -422,6 +515,9 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpDelete('/comments/posts/([0-9]+)')]
|
#[HttpDelete('/comments/posts/([0-9]+)')]
|
||||||
public function deletePost(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array|string {
|
public function deletePost(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array|string {
|
||||||
try {
|
try {
|
||||||
|
@ -448,6 +544,9 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[]|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpPost('/comments/posts/([0-9]+)/restore')]
|
#[HttpPost('/comments/posts/([0-9]+)/restore')]
|
||||||
public function postPostRestore(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
public function postPostRestore(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
||||||
if(!$this->getGlobalPerms()->check(Perm::G_COMMENTS_DELETE_ANY))
|
if(!$this->getGlobalPerms()->check(Perm::G_COMMENTS_DELETE_ANY))
|
||||||
|
@ -470,6 +569,9 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[]|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpPost('/comments/posts/([0-9]+)/nuke')]
|
#[HttpPost('/comments/posts/([0-9]+)/nuke')]
|
||||||
public function postPostNuke(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
public function postPostNuke(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
||||||
if(!$this->getGlobalPerms()->check(Perm::G_COMMENTS_DELETE_ANY))
|
if(!$this->getGlobalPerms()->check(Perm::G_COMMENTS_DELETE_ANY))
|
||||||
|
@ -492,6 +594,13 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{
|
||||||
|
* vote: int,
|
||||||
|
* positive: int,
|
||||||
|
* negative: int,
|
||||||
|
* }|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpPost('/comments/posts/([0-9]+)/vote')]
|
#[HttpPost('/comments/posts/([0-9]+)/vote')]
|
||||||
public function postPostVote(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
public function postPostVote(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
||||||
if(!($request->content instanceof FormHttpContent))
|
if(!($request->content instanceof FormHttpContent))
|
||||||
|
@ -533,6 +642,13 @@ class CommentsRoutes implements RouteHandler, UrlSource {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{
|
||||||
|
* vote: int,
|
||||||
|
* positive: int,
|
||||||
|
* negative: int,
|
||||||
|
* }|array{error: array{name: string, text: string}}
|
||||||
|
*/
|
||||||
#[HttpDelete('/comments/posts/([0-9]+)/vote')]
|
#[HttpDelete('/comments/posts/([0-9]+)/vote')]
|
||||||
public function deletePostVote(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
public function deletePostVote(HttpResponseBuilder $response, HttpRequest $request, string $commentId): array {
|
||||||
if(!$this->getGlobalPerms()->check(Perm::G_COMMENTS_VOTE))
|
if(!$this->getGlobalPerms()->check(Perm::G_COMMENTS_VOTE))
|
||||||
|
|
|
@ -71,7 +71,7 @@ class ForumPostsData {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ForumCategoryInfo|string|null|array<ForumCategoryInfo|string|int> $categoryInfo
|
* @param ForumCategoryInfo|string|null|array<ForumCategoryInfo|string|int> $categoryInfo
|
||||||
* @param ?array{type?: string, author?: string, after?: string, query_string?: string} $searchQuery
|
* @param ?array{type?: string, author?: string, after?: string, query_string?: string, topic?: string} $searchQuery
|
||||||
* @return \Iterator<int, ForumPostInfo>|ForumPostInfo[]
|
* @return \Iterator<int, ForumPostInfo>|ForumPostInfo[]
|
||||||
*/
|
*/
|
||||||
public function getPosts(
|
public function getPosts(
|
||||||
|
|
|
@ -15,7 +15,7 @@ class ArrayJWKSet implements JWKSet {
|
||||||
return $this->keys[array_key_first($this->keys)];
|
return $this->keys[array_key_first($this->keys)];
|
||||||
|
|
||||||
foreach($this->keys as $key)
|
foreach($this->keys as $key)
|
||||||
if($algo !== null && $key->algo !== null && hash_equals($key->algo, $algo))
|
if($key->algo !== null && hash_equals($key->algo, $algo))
|
||||||
return $key;
|
return $key;
|
||||||
|
|
||||||
throw new RuntimeException('could not find a key that matched the requested algorithm');
|
throw new RuntimeException('could not find a key that matched the requested algorithm');
|
||||||
|
@ -31,6 +31,9 @@ class ArrayJWKSet implements JWKSet {
|
||||||
return $key;
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|mixed[]|object $json
|
||||||
|
*/
|
||||||
public static function decodeJson(string|array|object $json): ArrayJWKSet {
|
public static function decodeJson(string|array|object $json): ArrayJWKSet {
|
||||||
if(is_object($json))
|
if(is_object($json))
|
||||||
$json = (array)$json;
|
$json = (array)$json;
|
||||||
|
|
|
@ -8,15 +8,19 @@ use Index\{UriBase64,XDateTime};
|
||||||
|
|
||||||
// based on https://github.com/firebase/php-jwt/blob/8f718f4dfc9c5d5f0c994cdfd103921b43592712/src/JWT.php
|
// based on https://github.com/firebase/php-jwt/blob/8f718f4dfc9c5d5f0c994cdfd103921b43592712/src/JWT.php
|
||||||
class JWT {
|
class JWT {
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed>|object $payload
|
||||||
|
* @param array<string, mixed>|object|null $headers
|
||||||
|
*/
|
||||||
public static function encode(
|
public static function encode(
|
||||||
array|object $payload,
|
array|object $payload,
|
||||||
JWK $key,
|
JWK $key,
|
||||||
?string $alg = null,
|
?string $alg = null,
|
||||||
array|object|null $header = null
|
array|object|null $headers = null
|
||||||
): string {
|
): string {
|
||||||
$head = ['typ' => 'JWT'];
|
$head = ['typ' => 'JWT'];
|
||||||
if($header !== null)
|
if($headers !== null)
|
||||||
$head = array_merge($head, (array)$header);
|
$head = array_merge($head, (array)$headers);
|
||||||
$head['alg'] = $alg ?? $key->algo;
|
$head['alg'] = $alg ?? $key->algo;
|
||||||
if($head['alg'] === null)
|
if($head['alg'] === null)
|
||||||
throw new InvalidArgumentException('$key does not provide a default algorithm, $alg must be specified');
|
throw new InvalidArgumentException('$key does not provide a default algorithm, $alg must be specified');
|
||||||
|
@ -29,6 +33,10 @@ class JWT {
|
||||||
return $encoded;
|
return $encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed>|object|null $headers
|
||||||
|
* @return array<string, mixed>|object
|
||||||
|
*/
|
||||||
public static function decode(
|
public static function decode(
|
||||||
string $token,
|
string $token,
|
||||||
JWKSet $keys,
|
JWKSet $keys,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu\JWT;
|
namespace Misuzu\JWT;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use phpseclib3\Crypt\Common\{AsymmetricKey,PrivateKey,PublicKey};
|
use phpseclib3\Crypt\Common\{AsymmetricKey,PrivateKey,PublicKey};
|
||||||
|
|
||||||
|
@ -35,10 +36,11 @@ class SecLibJWK implements JWK {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sign(string $data, SecLibJWKAlgo|string|null $algo = null): string {
|
public function sign(string $data, SecLibJWKAlgo|string|null $algo = null): string {
|
||||||
if(!($this->key instanceof PrivateKey))
|
$key = $this->resolveAlgo($algo)->transform($this->key);
|
||||||
|
if(!($key instanceof PrivateKey))
|
||||||
throw new RuntimeException('this instance does not have a private key, it can only be used to verify');
|
throw new RuntimeException('this instance does not have a private key, it can only be used to verify');
|
||||||
|
|
||||||
return $this->resolveAlgo($algo)->transform($this->key)->sign($data);
|
return $key->sign($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verify(string $data, string $signature, SecLibJWKAlgo|string|null $algo = null): bool {
|
public function verify(string $data, string $signature, SecLibJWKAlgo|string|null $algo = null): bool {
|
||||||
|
|
|
@ -29,25 +29,29 @@ class SecLibJWKAlgo {
|
||||||
return new SecLibJWKAlgo($algo, self::$algos[$algo]);
|
return new SecLibJWKAlgo($algo, self::$algos[$algo]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function ensureRSAKey(AsymmetricKey $key): void {
|
public static function ensureRSAKey(AsymmetricKey $key): RSA {
|
||||||
if(!($key instanceof RSA))
|
if($key instanceof RSA)
|
||||||
throw new InvalidArgumentException('$key must be an RSA key');
|
return $key;
|
||||||
|
|
||||||
|
throw new InvalidArgumentException('$key must be an RSA key');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function applyPKCS1Padding(AsymmetricKey $key): AsymmetricKey {
|
public static function applyPKCS1Padding(RSA $key): RSA {
|
||||||
return $key->withPadding(RSA::SIGNATURE_PKCS1);
|
return $key->withPadding(RSA::SIGNATURE_PKCS1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function applyPSSPadding(AsymmetricKey $key): AsymmetricKey {
|
public static function applyPSSPadding(RSA $key): RSA {
|
||||||
return $key->withPadding(RSA::SIGNATURE_PSS);
|
return $key->withPadding(RSA::SIGNATURE_PSS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function ensureECKey(AsymmetricKey $key): void {
|
public static function ensureECKey(AsymmetricKey $key): EC {
|
||||||
if(!($key instanceof EC))
|
if($key instanceof EC)
|
||||||
throw new InvalidArgumentException('$key must be an EC key');
|
return $key;
|
||||||
|
|
||||||
|
throw new InvalidArgumentException('$key must be an EC key');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function applyIEEESignatureFormat(AsymmetricKey $key): AsymmetricKey {
|
public static function applyIEEESignatureFormat(EC $key): EC {
|
||||||
return $key->withSignatureFormat('IEEE');
|
return $key->withSignatureFormat('IEEE');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,70 +60,70 @@ class SecLibJWKAlgo {
|
||||||
return array_keys(self::$algos);
|
return array_keys(self::$algos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformRS256(AsymmetricKey $key): AsymmetricKey {
|
public static function transformRS256(AsymmetricKey $key): RSA {
|
||||||
self::ensureRSAKey($key);
|
$key = self::ensureRSAKey($key);
|
||||||
return self::applyPKCS1Padding($key)->withHash('sha256');
|
return self::applyPKCS1Padding($key)->withHash('sha256');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformRS384(AsymmetricKey $key): AsymmetricKey {
|
public static function transformRS384(AsymmetricKey $key): RSA {
|
||||||
self::ensureRSAKey($key);
|
$key = self::ensureRSAKey($key);
|
||||||
return self::applyPKCS1Padding($key)->withHash('sha384');
|
return self::applyPKCS1Padding($key)->withHash('sha384');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformRS512(AsymmetricKey $key): AsymmetricKey {
|
public static function transformRS512(AsymmetricKey $key): RSA {
|
||||||
self::ensureRSAKey($key);
|
$key = self::ensureRSAKey($key);
|
||||||
return self::applyPKCS1Padding($key)->withHash('sha512');
|
return self::applyPKCS1Padding($key)->withHash('sha512');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformPS256(AsymmetricKey $key): AsymmetricKey {
|
public static function transformPS256(AsymmetricKey $key): RSA {
|
||||||
self::ensureRSAKey($key);
|
$key = self::ensureRSAKey($key);
|
||||||
return self::applyPSSPadding($key)->withHash('sha512');
|
return self::applyPSSPadding($key)->withHash('sha512');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformPS384(AsymmetricKey $key): AsymmetricKey {
|
public static function transformPS384(AsymmetricKey $key): RSA {
|
||||||
self::ensureRSAKey($key);
|
$key = self::ensureRSAKey($key);
|
||||||
return self::applyPSSPadding($key)->withHash('sha512');
|
return self::applyPSSPadding($key)->withHash('sha512');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformPS512(AsymmetricKey $key): AsymmetricKey {
|
public static function transformPS512(AsymmetricKey $key): RSA {
|
||||||
self::ensureRSAKey($key);
|
$key = self::ensureRSAKey($key);
|
||||||
return self::applyPSSPadding($key)->withHash('sha512');
|
return self::applyPSSPadding($key)->withHash('sha512');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformES256(AsymmetricKey $key): AsymmetricKey {
|
public static function transformES256(AsymmetricKey $key): EC {
|
||||||
self::ensureECKey($key);
|
$key = self::ensureECKey($key);
|
||||||
if($key->getCurve() !== 'secp256r1')
|
if($key->getCurve() !== 'secp256r1')
|
||||||
throw new InvalidArgumentException('curve must be secp256r1');
|
throw new InvalidArgumentException('curve must be secp256r1');
|
||||||
|
|
||||||
return self::applyIEEESignatureFormat($key)->withHash('sha256');
|
return self::applyIEEESignatureFormat($key)->withHash('sha256');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformES256K(AsymmetricKey $key): AsymmetricKey {
|
public static function transformES256K(AsymmetricKey $key): EC {
|
||||||
self::ensureECKey($key);
|
$key = self::ensureECKey($key);
|
||||||
if($key->getCurve() !== 'secp256k1')
|
if($key->getCurve() !== 'secp256k1')
|
||||||
throw new InvalidArgumentException('curve must be secp256k1');
|
throw new InvalidArgumentException('curve must be secp256k1');
|
||||||
|
|
||||||
return self::applyIEEESignatureFormat($key)->withHash('sha256');
|
return self::applyIEEESignatureFormat($key)->withHash('sha256');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformES384(AsymmetricKey $key): AsymmetricKey {
|
public static function transformES384(AsymmetricKey $key): EC {
|
||||||
self::ensureECKey($key);
|
$key = self::ensureECKey($key);
|
||||||
if($key->getCurve() !== 'secp384r1')
|
if($key->getCurve() !== 'secp384r1')
|
||||||
throw new InvalidArgumentException('curve must be secp384r1');
|
throw new InvalidArgumentException('curve must be secp384r1');
|
||||||
|
|
||||||
return self::applyIEEESignatureFormat($key)->withHash('sha384');
|
return self::applyIEEESignatureFormat($key)->withHash('sha384');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformES512(AsymmetricKey $key): AsymmetricKey {
|
public static function transformES512(AsymmetricKey $key): EC {
|
||||||
self::ensureECKey($key);
|
$key = self::ensureECKey($key);
|
||||||
if($key->getCurve() !== 'secp521r1')
|
if($key->getCurve() !== 'secp521r1')
|
||||||
throw new InvalidArgumentException('curve must be secp521r1');
|
throw new InvalidArgumentException('curve must be secp521r1');
|
||||||
|
|
||||||
return self::applyIEEESignatureFormat($key)->withHash('sha512');
|
return self::applyIEEESignatureFormat($key)->withHash('sha512');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function transformEdDSA(AsymmetricKey $key): AsymmetricKey {
|
public static function transformEdDSA(AsymmetricKey $key): EC {
|
||||||
self::ensureECKey($key);
|
$key = self::ensureECKey($key);
|
||||||
if($key->getCurve() !== 'Ed25519' && $key->getCurve() !== 'Ed448')
|
if($key->getCurve() !== 'Ed25519' && $key->getCurve() !== 'Ed448')
|
||||||
throw new InvalidArgumentException('curve must be Ed25519 or Ed448');
|
throw new InvalidArgumentException('curve must be Ed25519 or Ed448');
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
|
||||||
use Index\Syndication\FeedBuilder;
|
use Index\Syndication\FeedBuilder;
|
||||||
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
|
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
|
||||||
use Misuzu\{Pagination,SiteInfo,Template};
|
use Misuzu\{Pagination,SiteInfo,Template};
|
||||||
use Misuzu\Auth\AuthInfo;
|
|
||||||
use Misuzu\Comments\CommentsContext;
|
use Misuzu\Comments\CommentsContext;
|
||||||
use Misuzu\Parsers\{Parsers,TextFormat};
|
use Misuzu\Parsers\{Parsers,TextFormat};
|
||||||
use Misuzu\Users\{UsersContext,UserInfo};
|
use Misuzu\Users\{UsersContext,UserInfo};
|
||||||
|
@ -18,7 +17,6 @@ class NewsRoutes implements RouteHandler, UrlSource {
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private SiteInfo $siteInfo,
|
private SiteInfo $siteInfo,
|
||||||
private AuthInfo $authInfo,
|
|
||||||
private UrlRegistry $urls,
|
private UrlRegistry $urls,
|
||||||
private UsersContext $usersCtx,
|
private UsersContext $usersCtx,
|
||||||
private CommentsContext $commentsCtx,
|
private CommentsContext $commentsCtx,
|
||||||
|
|
|
@ -3,6 +3,7 @@ namespace Misuzu\OAuth2;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\XArray;
|
use Index\XArray;
|
||||||
|
use Index\Colour\{Colour,ColourRgb};
|
||||||
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,UrlRegistry,UrlSource,UrlSourceCommon};
|
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
|
||||||
|
@ -48,6 +49,14 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|array{
|
||||||
|
* resource: string,
|
||||||
|
* authorization_servers: string[],
|
||||||
|
* scopes_supported: string[],
|
||||||
|
* bearer_methods_supported: string[],
|
||||||
|
* }
|
||||||
|
*/
|
||||||
#[HttpOptions('/.well-known/oauth-protected-resource')]
|
#[HttpOptions('/.well-known/oauth-protected-resource')]
|
||||||
#[HttpGet('/.well-known/oauth-protected-resource')]
|
#[HttpGet('/.well-known/oauth-protected-resource')]
|
||||||
public function getWellKnownProtectedResource(HttpResponseBuilder $response, HttpRequest $request): array|int {
|
public function getWellKnownProtectedResource(HttpResponseBuilder $response, HttpRequest $request): array|int {
|
||||||
|
@ -63,6 +72,24 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|array{
|
||||||
|
* issuer: string,
|
||||||
|
* authorization_endpoint: string,
|
||||||
|
* token_endpoint: string,
|
||||||
|
* jwks_uri: string,
|
||||||
|
* scopes_supported: string[],
|
||||||
|
* response_types_supported: string[],
|
||||||
|
* response_modes_supported: string[],
|
||||||
|
* grant_types_supported: string[],
|
||||||
|
* token_endpoint_auth_methods_supported: string[],
|
||||||
|
* revocation_endpoint?: string,
|
||||||
|
* revocation_endpoint_auth_methods_supported?: string[],
|
||||||
|
* introspection_endpoint?: string,
|
||||||
|
* introspection_endpoint_auth_methods_supported?: string[],
|
||||||
|
* code_challenge_methods_supported: string[],
|
||||||
|
* }
|
||||||
|
*/
|
||||||
#[HttpOptions('/.well-known/oauth-authorization-server')]
|
#[HttpOptions('/.well-known/oauth-authorization-server')]
|
||||||
#[HttpGet('/.well-known/oauth-authorization-server')]
|
#[HttpGet('/.well-known/oauth-authorization-server')]
|
||||||
public function getWellKnownAuthorizationServer(HttpResponseBuilder $response, HttpRequest $request): array|int {
|
public function getWellKnownAuthorizationServer(HttpResponseBuilder $response, HttpRequest $request): array|int {
|
||||||
|
@ -104,6 +131,21 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|array{
|
||||||
|
* issuer: string,
|
||||||
|
* authorization_endpoint: string,
|
||||||
|
* token_endpoint: string,
|
||||||
|
* userinfo_endpoint: string,
|
||||||
|
* jwks_uri: string,
|
||||||
|
* response_types_supported: string[],
|
||||||
|
* response_modes_supported: string[],
|
||||||
|
* grant_types_supported: string[],
|
||||||
|
* subject_types_supported: string[],
|
||||||
|
* id_token_signing_alg_values_supported: string[],
|
||||||
|
* token_endpoint_auth_methods_supported: string[],
|
||||||
|
* }
|
||||||
|
*/
|
||||||
#[HttpOptions('/.well-known/openid-configuration')]
|
#[HttpOptions('/.well-known/openid-configuration')]
|
||||||
#[HttpGet('/.well-known/openid-configuration')]
|
#[HttpGet('/.well-known/openid-configuration')]
|
||||||
public function getWellKnown(HttpResponseBuilder $response, HttpRequest $request): array|int {
|
public function getWellKnown(HttpResponseBuilder $response, HttpRequest $request): array|int {
|
||||||
|
@ -125,13 +167,19 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
|
||||||
'jwks_uri' => sprintf('%s%s', $this->siteInfo->url, $this->urls->format('oauth2-jwks')),
|
'jwks_uri' => sprintf('%s%s', $this->siteInfo->url, $this->urls->format('oauth2-jwks')),
|
||||||
'response_types_supported' => ['code', 'code id_token'],
|
'response_types_supported' => ['code', 'code id_token'],
|
||||||
'response_modes_supported' => ['query'],
|
'response_modes_supported' => ['query'],
|
||||||
'grant_types_supported' => ['authorization_code'],
|
'grant_types_supported' => [
|
||||||
|
'authorization_code',
|
||||||
|
//'client_credentials', <-- supported but makes NO sense for openid
|
||||||
|
'refresh_token',
|
||||||
|
'urn:ietf:params:oauth:grant-type:device_code',
|
||||||
|
],
|
||||||
'subject_types_supported' => ['public'],
|
'subject_types_supported' => ['public'],
|
||||||
'id_token_signing_alg_values_supported' => $signingAlgs,
|
'id_token_signing_alg_values_supported' => $signingAlgs,
|
||||||
'token_endpoint_auth_methods_supported' => ['client_secret_basic', 'client_secret_post'],
|
'token_endpoint_auth_methods_supported' => ['none', 'client_secret_basic', 'client_secret_post'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return int|array{keys?: array<string, string>} */
|
||||||
#[HttpOptions('/oauth2/jwks.json')]
|
#[HttpOptions('/oauth2/jwks.json')]
|
||||||
#[HttpOptions('/openid/jwks.json')]
|
#[HttpOptions('/openid/jwks.json')]
|
||||||
#[HttpGet('/oauth2/jwks.json')]
|
#[HttpGet('/oauth2/jwks.json')]
|
||||||
|
@ -207,7 +255,7 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{
|
* @return int|array{
|
||||||
* access_token: string,
|
* access_token: string,
|
||||||
* token_type: 'Bearer',
|
* token_type: 'Bearer',
|
||||||
* expires_in?: int,
|
* expires_in?: int,
|
||||||
|
@ -324,6 +372,28 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|array{
|
||||||
|
* sub: string,
|
||||||
|
* name?: string,
|
||||||
|
* nickname?: string,
|
||||||
|
* preferred_username?: string,
|
||||||
|
* profile?: string,
|
||||||
|
* picture?: string,
|
||||||
|
* zoneinfo?: string,
|
||||||
|
* birthdate?: string,
|
||||||
|
* website?: string,
|
||||||
|
* 'http://railgun.sh/country_code'?: string,
|
||||||
|
* 'http://railgun.sh/colour_raw'?: int,
|
||||||
|
* 'http://railgun.sh/colour_css'?: string,
|
||||||
|
* 'http://railgun.sh/rank'?: int,
|
||||||
|
* 'http://railgun.sh/banned'?: bool,
|
||||||
|
* 'http://railgun.sh/roles'?: string[],
|
||||||
|
* 'http://railgun.sh/is_super'?: bool,
|
||||||
|
* email?: string,
|
||||||
|
* email_verified?: bool,
|
||||||
|
* }|array{ error: string, error_description: string }
|
||||||
|
*/
|
||||||
#[HttpOptions('/oauth2/userinfo')]
|
#[HttpOptions('/oauth2/userinfo')]
|
||||||
#[HttpOptions('/openid/userinfo')]
|
#[HttpOptions('/openid/userinfo')]
|
||||||
#[HttpGet('/oauth2/userinfo')]
|
#[HttpGet('/oauth2/userinfo')]
|
||||||
|
|
|
@ -80,6 +80,17 @@ class OAuth2Context {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{
|
||||||
|
* iss: string,
|
||||||
|
* sub: string,
|
||||||
|
* aud: string,
|
||||||
|
* exp: int,
|
||||||
|
* iat: int,
|
||||||
|
* auth_time: int,
|
||||||
|
* nonce?: string,
|
||||||
|
* }
|
||||||
|
*/
|
||||||
public static function getDataForIdToken(
|
public static function getDataForIdToken(
|
||||||
SiteInfo $siteInfo,
|
SiteInfo $siteInfo,
|
||||||
AppInfo $appInfo,
|
AppInfo $appInfo,
|
||||||
|
|
|
@ -38,7 +38,11 @@ class OAuth2Keys {
|
||||||
if($body === false)
|
if($body === false)
|
||||||
throw new RuntimeException(sprintf('public key "%s" could not be read', $keyId));
|
throw new RuntimeException(sprintf('public key "%s" could not be read', $keyId));
|
||||||
|
|
||||||
return PublicKeyLoader::loadPublicKey($body);
|
$key = PublicKeyLoader::loadPublicKey($body);
|
||||||
|
if(!($key instanceof AsymmetricKey))
|
||||||
|
throw new RuntimeException(sprintf('public key "%s" could not be loaded', $keyId));
|
||||||
|
|
||||||
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPrivateKey(string $keyId): AsymmetricKey&PrivateKey {
|
public function getPrivateKey(string $keyId): AsymmetricKey&PrivateKey {
|
||||||
|
@ -50,7 +54,11 @@ class OAuth2Keys {
|
||||||
if($body === false)
|
if($body === false)
|
||||||
throw new RuntimeException(sprintf('private key "%s" could not be read', $keyId));
|
throw new RuntimeException(sprintf('private key "%s" could not be read', $keyId));
|
||||||
|
|
||||||
return PublicKeyLoader::loadPrivateKey($body);
|
$key = PublicKeyLoader::loadPrivateKey($body);
|
||||||
|
if(!($key instanceof AsymmetricKey))
|
||||||
|
throw new RuntimeException(sprintf('private key "%s" could not be loaded', $keyId));
|
||||||
|
|
||||||
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPublicKeySet(): JWKSet {
|
public function getPublicKeySet(): JWKSet {
|
||||||
|
@ -77,19 +85,23 @@ class OAuth2Keys {
|
||||||
return new ArrayJWKSet($keys);
|
return new ArrayJWKSet($keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array<string, mixed[]> */
|
/** @return array{keys?: array<string, string>} */
|
||||||
public function getPublicKeysForJson(): array {
|
public function getPublicKeysForJson(): array {
|
||||||
$keys = [];
|
$keys = [];
|
||||||
|
|
||||||
foreach($this->keys as $keyInfo)
|
foreach($this->keys as $keyInfo) {
|
||||||
|
$options = [
|
||||||
|
'kid' => $keyInfo->id,
|
||||||
|
'use' => 'sig',
|
||||||
|
];
|
||||||
|
if($keyInfo->alg !== null)
|
||||||
|
$options['alg'] = $keyInfo->alg;
|
||||||
|
|
||||||
$keys = array_merge_recursive(
|
$keys = array_merge_recursive(
|
||||||
$keys,
|
$keys,
|
||||||
json_decode($this->getPublicKey($keyInfo->id)->toString('JWK', [
|
json_decode($this->getPublicKey($keyInfo->id)->toString('JWK', $options), true)
|
||||||
'kid' => $keyInfo->id,
|
|
||||||
'use' => 'sig',
|
|
||||||
'alg' => $keyInfo->alg,
|
|
||||||
]), true)
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return $keys;
|
return $keys;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ final class OAuth2WebRoutes implements RouteHandler, UrlSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int|array{
|
* @return int|array{
|
||||||
* error: 'auth'|'csrf'|'method'|'length'|'client'|'scope'|'format'|'required'|'authorise',
|
* error: 'auth'|'csrf'|'method'|'length'|'client'|'scope'|'format'|'required'|'authorise'|'resptype',
|
||||||
* scope?: string,
|
* scope?: string,
|
||||||
* reason?: string,
|
* reason?: string,
|
||||||
* }|array{
|
* }|array{
|
||||||
|
|
|
@ -5,6 +5,10 @@ use InvalidArgumentException;
|
||||||
use Index\XArray;
|
use Index\XArray;
|
||||||
|
|
||||||
class BasicWebFingerLink implements WebFingerLink {
|
class BasicWebFingerLink implements WebFingerLink {
|
||||||
|
/**
|
||||||
|
* @param WebFingerTitle[] $titles
|
||||||
|
* @param WebFingerProperty[] $properties
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public private(set) string $relation,
|
public private(set) string $relation,
|
||||||
public private(set) ?string $type = null,
|
public private(set) ?string $type = null,
|
||||||
|
|
|
@ -4,6 +4,11 @@ namespace Misuzu\WebFinger;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
class BasicWebFingerResourceInfo implements WebFingerResourceInfo {
|
class BasicWebFingerResourceInfo implements WebFingerResourceInfo {
|
||||||
|
/**
|
||||||
|
* @param string[] $aliases
|
||||||
|
* @param WebFingerProperty[] $properties
|
||||||
|
* @param WebFingerLink[] $links
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public private(set) ?string $subject = null,
|
public private(set) ?string $subject = null,
|
||||||
public private(set) array $aliases = [],
|
public private(set) array $aliases = [],
|
||||||
|
|
|
@ -10,7 +10,7 @@ class WebFingerRegistry {
|
||||||
$this->resolvers[] = $resolver;
|
$this->resolvers[] = $resolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return WebFingerResolver[] */
|
/** @return WebFingerResourceInfo[] */
|
||||||
public function resolve(string $uri): array {
|
public function resolve(string $uri): array {
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
namespace Misuzu\WebFinger;
|
namespace Misuzu\WebFinger;
|
||||||
|
|
||||||
interface WebFingerResolver {
|
interface WebFingerResolver {
|
||||||
|
/** @param array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path: string, query?: string, fragment?: string} $components */
|
||||||
public function resolve(string $uri, array $components): ?WebFingerResourceInfo;
|
public function resolve(string $uri, array $components): ?WebFingerResourceInfo;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,10 @@ class WebFingerRoutes implements RouteHandler {
|
||||||
private WebFingerRegistry $registry
|
private WebFingerRegistry $registry
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/** @param WebFingerProperty[] $propInfos */
|
/**
|
||||||
|
* @param WebFingerProperty[] $propInfos
|
||||||
|
* @return array<string, ?string>
|
||||||
|
*/
|
||||||
private static function extractProps(array $propInfos): array {
|
private static function extractProps(array $propInfos): array {
|
||||||
$props = [];
|
$props = [];
|
||||||
foreach($propInfos as $propInfo)
|
foreach($propInfos as $propInfo)
|
||||||
|
@ -20,7 +23,10 @@ class WebFingerRoutes implements RouteHandler {
|
||||||
return $props;
|
return $props;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param WebFingerTitle[] $titleInfos */
|
/**
|
||||||
|
* @param WebFingerTitle[] $titleInfos
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
private static function extractTitles(array $titleInfos): array {
|
private static function extractTitles(array $titleInfos): array {
|
||||||
$titles = [];
|
$titles = [];
|
||||||
foreach($titleInfos as $titleInfo)
|
foreach($titleInfos as $titleInfo)
|
||||||
|
@ -28,6 +34,10 @@ class WebFingerRoutes implements RouteHandler {
|
||||||
return $titles;
|
return $titles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param WebFingerLink[] $linkInfos
|
||||||
|
* @return array{rel: string, type?: string, href?: string, titles?: array<string, string>, properties?: array<string, ?string>}[]
|
||||||
|
*/
|
||||||
private static function extractLinks(array $linkInfos): array {
|
private static function extractLinks(array $linkInfos): array {
|
||||||
$links = [];
|
$links = [];
|
||||||
foreach($linkInfos as $linkInfo) {
|
foreach($linkInfos as $linkInfo) {
|
||||||
|
@ -45,6 +55,20 @@ class WebFingerRoutes implements RouteHandler {
|
||||||
return $links;
|
return $links;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|array{
|
||||||
|
* subject?: string,
|
||||||
|
* aliases?: string[],
|
||||||
|
* properties?: array<string, ?string>,
|
||||||
|
* links?: array{
|
||||||
|
* rel: string,
|
||||||
|
* type?: string,
|
||||||
|
* href?: string,
|
||||||
|
* titles?: array<string, string>,
|
||||||
|
* properties?: array<string, ?string>
|
||||||
|
* }[]
|
||||||
|
* }
|
||||||
|
*/
|
||||||
#[HttpOptions('/.well-known/webfinger')]
|
#[HttpOptions('/.well-known/webfinger')]
|
||||||
#[HttpGet('/.well-known/webfinger')]
|
#[HttpGet('/.well-known/webfinger')]
|
||||||
public function getWebFinger(HttpResponseBuilder $response, HttpRequest $request): array|int {
|
public function getWebFinger(HttpResponseBuilder $response, HttpRequest $request): array|int {
|
||||||
|
|
Loading…
Add table
Reference in a new issue