Files
breadcrumb-the-shire/lib/Service/Tenant/TenantScopeService.php
fs 25370a1a55 add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00

140 lines
4.8 KiB
PHP

<?php
namespace MintyPHP\Service\Tenant;
use MintyPHP\Repository\Org\DepartmentRepository;
use MintyPHP\Repository\Tenant\UserTenantRepository;
use MintyPHP\Repository\Tenant\TenantRepository;
use MintyPHP\Service\Access\PermissionService;
class TenantScopeService
{
public static function isStrict(): bool
{
return defined('TENANT_SCOPE_STRICT') ? (bool) TENANT_SCOPE_STRICT : true;
}
public static function getUserTenantIds(int $userId, bool $onlyActive = true): array
{
if ($userId <= 0) {
return [];
}
if (self::canBypass($userId)) {
$ids = TenantRepository::listIds();
} else {
$ids = array_values(array_unique(array_map('intval', UserTenantRepository::listTenantIdsByUserId($userId))));
}
if ($onlyActive) {
return self::filterActiveTenantIds($ids);
}
return $ids;
}
public static function canAccess(string $resource, int $resourceId, int $userId): bool
{
if ($resourceId <= 0 || $userId <= 0) {
return false;
}
if (self::canBypass($userId)) {
return true;
}
$resourceTenantIdsRaw = self::getResourceTenantIds($resource, $resourceId);
$userTenantIdsRaw = self::getUserTenantIds($userId, false);
$resourceTenantIds = self::filterActiveTenantIds($resourceTenantIdsRaw);
$userTenantIds = self::filterActiveTenantIds($userTenantIdsRaw);
if ($resourceTenantIdsRaw && !$resourceTenantIds) {
return false;
}
if (!$resourceTenantIds) {
return self::isStrict() ? false : true;
}
if (!$userTenantIds) {
return false;
}
return (bool) array_intersect($resourceTenantIds, $userTenantIds);
}
public static function filterTenantIdsForUser(array $tenantIds, int $userId): array
{
$tenantIds = array_values(array_unique(array_map('intval', $tenantIds)));
$userTenantIds = self::getUserTenantIds($userId);
if (!$userTenantIds) {
return [];
}
return array_values(array_intersect($tenantIds, $userTenantIds));
}
public static function mergeTenantIdsPreservingOutOfScope(array $requestedTenantIds, array $existingTenantIds, array $allowedTenantIds): array
{
$requestedTenantIds = array_values(array_unique(array_map('intval', $requestedTenantIds)));
$existingTenantIds = array_values(array_unique(array_map('intval', $existingTenantIds)));
$allowedTenantIds = array_values(array_unique(array_map('intval', $allowedTenantIds)));
if (!$allowedTenantIds) {
return $existingTenantIds;
}
$outOfScope = array_values(array_diff($existingTenantIds, $allowedTenantIds));
return array_values(array_unique(array_merge($requestedTenantIds, $outOfScope)));
}
public static function hasGlobalAccess(int $userId): bool
{
return self::canBypass($userId);
}
public static function resourceBelongsToTenant(string $resource, int $resourceId, int $tenantId): bool
{
if ($resourceId <= 0 || $tenantId <= 0) {
return false;
}
$resourceTenantIds = self::getResourceTenantIds($resource, $resourceId);
if (!$resourceTenantIds) {
return false;
}
$activeResourceTenantIds = self::filterActiveTenantIds($resourceTenantIds);
if (!$activeResourceTenantIds) {
return false;
}
return in_array($tenantId, $activeResourceTenantIds, true);
}
private static function getResourceTenantIds(string $resource, int $resourceId): array
{
$resource = strtolower(trim($resource));
if ($resource === 'tenants') {
return $resourceId > 0 ? [$resourceId] : [];
}
if ($resource === 'users') {
return array_values(array_unique(array_map('intval', UserTenantRepository::listTenantIdsByUserId($resourceId))));
}
if ($resource === 'departments') {
$department = DepartmentRepository::find($resourceId);
$tenantId = (int) ($department['tenant_id'] ?? 0);
return $tenantId > 0 ? [$tenantId] : [];
}
return [];
}
private static function filterActiveTenantIds(array $tenantIds): array
{
$tenantIds = array_values(array_unique(array_map('intval', $tenantIds)));
if (!$tenantIds) {
return [];
}
return TenantRepository::listActiveIdsByIds($tenantIds);
}
private static function canBypass(int $userId): bool
{
if ($userId <= 0) {
return false;
}
return PermissionService::userHas($userId, PermissionService::TENANTS_UPDATE)
|| PermissionService::userHas($userId, PermissionService::SETTINGS_UPDATE);
}
}