You've already forked Php-Template
fix: make Scope attribute repeatable and improve scope handling in middleware #21
12
src/Attributes/Guards/RequireAllScopes.php
Normal file
12
src/Attributes/Guards/RequireAllScopes.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Attributes\Guards;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
|
||||||
|
readonly class RequireAllScopes
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -6,13 +6,18 @@ namespace Siteworxpro\App\Attributes\Guards;
|
|||||||
|
|
||||||
use Attribute;
|
use Attribute;
|
||||||
|
|
||||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
|
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||||
readonly class Scope
|
readonly class Scope
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $scopes the required scopes
|
||||||
|
* @param string $claim the claim to check for scopes
|
||||||
|
* @param string $separator the separator used to split scopes in the claim
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private array $scopes = [],
|
private array $scopes = [],
|
||||||
private string $claim = 'scope',
|
private string $claim = 'scope',
|
||||||
private string $separator = ' ',
|
private string $separator = ' '
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ class IndexController extends Controller
|
|||||||
* @throws \JsonException
|
* @throws \JsonException
|
||||||
*/
|
*/
|
||||||
#[Guards\Jwt]
|
#[Guards\Jwt]
|
||||||
#[Guards\Scope(['get.index'])]
|
#[Guards\Scope(['get.index', 'status.check'])]
|
||||||
|
#[Guards\RequireAllScopes]
|
||||||
public function get(ServerRequest $request): ResponseInterface
|
public function get(ServerRequest $request): ResponseInterface
|
||||||
{
|
{
|
||||||
return JsonResponseFactory::createJsonResponse(['status_code' => 200, 'message' => 'Server is running']);
|
return JsonResponseFactory::createJsonResponse(['status_code' => 200, 'message' => 'Server is running']);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use League\Route\Dispatcher;
|
|||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Siteworxpro\App\Attributes\Guards\RequireAllScopes;
|
||||||
use Siteworxpro\App\Attributes\Guards\Scope;
|
use Siteworxpro\App\Attributes\Guards\Scope;
|
||||||
use Siteworxpro\App\Controllers\Controller;
|
use Siteworxpro\App\Controllers\Controller;
|
||||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||||
@@ -57,31 +58,49 @@ class ScopeMiddleware extends Middleware
|
|||||||
// Ensure the controller exists and the method is defined before reflecting.
|
// Ensure the controller exists and the method is defined before reflecting.
|
||||||
if (class_exists($class::class)) {
|
if (class_exists($class::class)) {
|
||||||
$reflectionClass = new \ReflectionClass($class);
|
$reflectionClass = new \ReflectionClass($class);
|
||||||
|
|
||||||
if ($reflectionClass->hasMethod($method)) {
|
if ($reflectionClass->hasMethod($method)) {
|
||||||
$reflectionMethod = $reflectionClass->getMethod($method);
|
$reflectionMethod = $reflectionClass->getMethod($method);
|
||||||
|
|
||||||
// Fetch all Scope attributes declared on the method.
|
// Fetch all Scope attributes declared on the method.
|
||||||
$attributes = $reflectionMethod->getAttributes(Scope::class);
|
$attributes = $reflectionMethod->getAttributes(Scope::class);
|
||||||
|
$requireAllAttributes = $reflectionMethod->getAttributes(RequireAllScopes::class);
|
||||||
|
|
||||||
|
if (empty($attributes)) {
|
||||||
|
// No scope attributes; delegate to the next handler.
|
||||||
|
return $handler->handle($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
$requiredScopes = [];
|
||||||
|
$userScopes = [];
|
||||||
|
$requireAll = false;
|
||||||
|
|
||||||
foreach ($attributes as $attribute) {
|
foreach ($attributes as $attribute) {
|
||||||
/** @var Scope $scopeInstance Concrete Scope attribute instance. */
|
/** @var Scope $scopeInstance Concrete Scope attribute instance. */
|
||||||
$scopeInstance = $attribute->newInstance();
|
$scopeInstance = $attribute->newInstance();
|
||||||
$requiredScopes = $scopeInstance->getScopes();
|
$requiredScopes = array_merge($requiredScopes, $scopeInstance->getScopes());
|
||||||
|
|
||||||
// Retrieve user scopes from the request (defaults to an empty array).
|
// If any attribute requires all scopes, set the flag.
|
||||||
$userScopes = $request->getAttribute($scopeInstance->getClaim(), []);
|
$requireAll = $requireAll || !empty($requireAllAttributes);
|
||||||
|
|
||||||
if (!is_array($userScopes)) {
|
$scopes = $request->getAttribute($scopeInstance->getClaim());
|
||||||
|
if (!is_array($scopes)) {
|
||||||
// If user scopes are not an array, treat as no scopes provided.
|
// If user scopes are not an array, treat as no scopes provided.
|
||||||
$userScopes = explode($scopeInstance->getSeparator(), (string) $userScopes);
|
$scopes = explode($scopeInstance->getSeparator(), (string) $scopes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$userScopes = array_merge(
|
||||||
|
$userScopes,
|
||||||
|
$scopes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userScopes = array_unique($userScopes);
|
||||||
|
|
||||||
// Deny if any required scope is missing from the user's scopes.
|
// Deny if any required scope is missing from the user's scopes.
|
||||||
if (
|
if (
|
||||||
array_any(
|
(!$requireAll && array_intersect($userScopes, $requiredScopes) === []) ||
|
||||||
$requiredScopes,
|
($requireAll && array_diff($requiredScopes, $userScopes) !== [])
|
||||||
fn($requiredScope) => !in_array($requiredScope, $userScopes, true)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return JsonResponseFactory::createJsonResponse([
|
return JsonResponseFactory::createJsonResponse([
|
||||||
'error' => 'insufficient_scope',
|
'error' => 'insufficient_scope',
|
||||||
@@ -91,7 +110,6 @@ class ScopeMiddleware extends Middleware
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// All checks passed; continue down the middleware pipeline.
|
// All checks passed; continue down the middleware pipeline.
|
||||||
return $handler->handle($request);
|
return $handler->handle($request);
|
||||||
|
|||||||
Reference in New Issue
Block a user