major update

This commit is contained in:
2026-03-04 15:56:58 +01:00
parent 16759a2732
commit 8f4dd5840d
478 changed files with 24313 additions and 8201 deletions

View File

@@ -2,13 +2,32 @@
namespace MintyPHP\Http;
use MintyPHP\Service\Access\AccessServicesFactory;
use MintyPHP\Repository\Access\RolePermissionRepository;
use MintyPHP\Repository\Access\UserRoleRepository;
use MintyPHP\Service\Access\AuthorizationService;
use MintyPHP\Service\Auth\ApiTokenService;
use MintyPHP\Service\Auth\AuthScopeGateway;
use MintyPHP\Service\Auth\AuthServicesFactory;
use MintyPHP\Service\User\UserServicesFactory;
use MintyPHP\Service\User\UserTenantContextService;
class ApiAuth
{
/** @var (callable(): AuthScopeGateway)|null */
private static $authScopeGatewayResolver = null;
/** @var (callable(): ApiTokenService)|null */
private static $apiTokenServiceResolver = null;
/** @var (callable(): UserRoleRepository)|null */
private static $userRoleRepositoryResolver = null;
/** @var (callable(): RolePermissionRepository)|null */
private static $rolePermissionRepositoryResolver = null;
/** @var (callable(): UserTenantContextService)|null */
private static $userTenantContextServiceResolver = null;
/** @var (callable(): AuthorizationService)|null */
private static $authorizationServiceResolver = null;
private static ?array $currentUser = null;
private static ?array $currentPermissions = null;
private static ?int $currentTenantId = null;
@@ -16,6 +35,22 @@ class ApiAuth
private static ?array $currentTokenRecord = null;
private static bool $authenticated = false;
public static function configure(
callable $authScopeGatewayResolver,
callable $apiTokenServiceResolver,
callable $userRoleRepositoryResolver,
callable $rolePermissionRepositoryResolver,
callable $userTenantContextServiceResolver,
callable $authorizationServiceResolver
): void {
self::$authScopeGatewayResolver = $authScopeGatewayResolver;
self::$apiTokenServiceResolver = $apiTokenServiceResolver;
self::$userRoleRepositoryResolver = $userRoleRepositoryResolver;
self::$rolePermissionRepositoryResolver = $rolePermissionRepositoryResolver;
self::$userTenantContextServiceResolver = $userTenantContextServiceResolver;
self::$authorizationServiceResolver = $authorizationServiceResolver;
}
/**
* Extract the full Bearer token from the Authorization header.
*/
@@ -49,19 +84,17 @@ class ApiAuth
return false;
}
$authServicesFactory = new AuthServicesFactory();
$scopeGateway = $authServicesFactory->createAuthScopeGateway();
$result = $authServicesFactory->createApiTokenService()->validate($bearerToken);
$scopeGateway = self::scopeGateway();
$result = self::apiTokenService()->validate($bearerToken);
if ($result === null) {
return false;
}
$user = $result['user'];
$userId = (int) ($user['id'] ?? 0);
$userServicesFactory = new UserServicesFactory();
$userRoleRepository = $userServicesFactory->createUserRoleRepository();
$rolePermissionRepository = (new AccessServicesFactory())->createRolePermissionRepository();
$userTenantContextService = $userServicesFactory->createUserTenantContextService();
$userRoleRepository = self::userRoleRepository();
$rolePermissionRepository = self::rolePermissionRepository();
$userTenantContextService = self::userTenantContextService();
// Load permissions directly (bypass session cache)
$roleIds = $userRoleRepository->listRoleIdsByUserId($userId);
@@ -110,9 +143,14 @@ class ApiAuth
return self::$currentPermissions ?? [];
}
public static function hasPermission(string $key): bool
public static function can(string $ability, array $context = []): bool
{
return in_array($key, self::$currentPermissions ?? [], true);
$decision = self::authorizationService()->authorize($ability, [
'actor_user_id' => self::userId(),
'scoped_tenant_id' => self::scopedTenantId(),
...$context,
]);
return $decision->isAllowed();
}
public static function tenantId(): ?int
@@ -146,7 +184,7 @@ class ApiAuth
*/
public static function requireResourceAccess(string $resource, int $resourceId): void
{
$scopeGateway = (new AuthServicesFactory())->createAuthScopeGateway();
$scopeGateway = self::scopeGateway();
if (!$scopeGateway->canAccess($resource, $resourceId, self::userId())) {
ApiResponse::notFound();
}
@@ -164,7 +202,7 @@ class ApiAuth
ApiResponse::forbidden();
}
$scopeGateway = (new AuthServicesFactory())->createAuthScopeGateway();
$scopeGateway = self::scopeGateway();
if (!$scopeGateway->resourceBelongsToTenant($resource, $resourceId, $tenantId)) {
ApiResponse::notFound();
}
@@ -175,8 +213,14 @@ class ApiAuth
*/
public static function canSelfManageTokens(): bool
{
return self::hasPermission(\MintyPHP\Service\Access\PermissionService::USERS_SELF_UPDATE)
|| self::hasPermission(\MintyPHP\Service\Access\PermissionService::API_TOKENS_MANAGE);
$decision = self::authorizationService()->authorize(
\MintyPHP\Service\Access\OperationsAuthorizationPolicy::ABILITY_API_TOKENS_SELF_MANAGE,
[
'actor_user_id' => self::userId(),
'scoped_tenant_id' => self::scopedTenantId(),
]
);
return $decision->isAllowed();
}
public static function requireSelfManageTokens(): void
@@ -185,4 +229,49 @@ class ApiAuth
ApiResponse::forbidden('api_tokens_self_manage_forbidden');
}
}
private static function scopeGateway(): AuthScopeGateway
{
return self::resolveDependency(self::$authScopeGatewayResolver, AuthScopeGateway::class, 'ApiAuth');
}
private static function apiTokenService(): ApiTokenService
{
return self::resolveDependency(self::$apiTokenServiceResolver, ApiTokenService::class, 'ApiAuth');
}
private static function userRoleRepository(): UserRoleRepository
{
return self::resolveDependency(self::$userRoleRepositoryResolver, UserRoleRepository::class, 'ApiAuth');
}
private static function rolePermissionRepository(): RolePermissionRepository
{
return self::resolveDependency(self::$rolePermissionRepositoryResolver, RolePermissionRepository::class, 'ApiAuth');
}
private static function userTenantContextService(): UserTenantContextService
{
return self::resolveDependency(self::$userTenantContextServiceResolver, UserTenantContextService::class, 'ApiAuth');
}
private static function authorizationService(): AuthorizationService
{
return self::resolveDependency(self::$authorizationServiceResolver, AuthorizationService::class, 'ApiAuth');
}
private static function resolveDependency(mixed $resolver, string $expectedClass, string $consumer): mixed
{
if (!is_callable($resolver)) {
throw new \RuntimeException($consumer . ' is not configured for dependency: ' . $expectedClass);
}
$service = $resolver();
if (!$service instanceof $expectedClass) {
throw new \RuntimeException($consumer . ' resolver returned invalid dependency: ' . $expectedClass);
}
return $service;
}
}