fix: make Scope attribute repeatable and improve scope handling in middleware
All checks were successful
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Successful in 4m18s
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Successful in 4m36s
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Successful in 4m33s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Successful in 4m24s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Successful in 4m28s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Successful in 3m5s

This commit is contained in:
2025-11-19 14:08:54 -05:00
parent 9b736eb879
commit 2f447305b9
2 changed files with 31 additions and 22 deletions

View File

@@ -6,7 +6,7 @@ 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
{ {
public function __construct( public function __construct(

View File

@@ -42,7 +42,7 @@ class ScopeMiddleware extends Middleware
*/ */
public function process( public function process(
ServerRequestInterface $request, ServerRequestInterface $request,
RequestHandlerInterface | Dispatcher $handler RequestHandlerInterface|Dispatcher $handler
): ResponseInterface { ): ResponseInterface {
// Attempt to resolve the route's callable [Controller instance, method name]. // Attempt to resolve the route's callable [Controller instance, method name].
$callable = $this->extractRouteCallable($handler); $callable = $this->extractRouteCallable($handler);
@@ -57,32 +57,42 @@ 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);
if (empty($attributes)) {
// No scope attributes; delegate to the next handler.
return $handler->handle($request);
}
$requiredScopes = [];
$userScopes = [];
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). $scopes = $request->getAttribute($scopeInstance->getClaim());
$userScopes = $request->getAttribute($scopeInstance->getClaim(), []); if (!is_array($scopes)) {
if (!is_array($userScopes)) {
// 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_intersect($userScopes, $requiredScopes) === []) {
array_any(
$requiredScopes,
fn($requiredScope) => !in_array($requiredScope, $userScopes, true)
)
) {
return JsonResponseFactory::createJsonResponse([ return JsonResponseFactory::createJsonResponse([
'error' => 'insufficient_scope', 'error' => 'insufficient_scope',
'error_description' => 'error_description' =>
@@ -91,7 +101,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);