TenantAtlas/apps/platform/tests/Feature/ProviderResources/ProviderResourceBindingAuthorizationTest.php
ahmido 04d0d6184f feat(resources): implement provider resource identity binding (#452)
Added `ProviderResourceBinding` model, migrations, policies, and supporting framework for canonical resource identity mapping as defined in Spec 381. This provides the structural capability to resolve baseline and posture discrepancies by binding logical entities across source providers to canonical identities.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #452
2026-06-15 18:45:38 +00:00

156 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineTenantAssignment;
use App\Models\InventoryItem;
use App\Models\ManagedEnvironment;
use App\Models\ProviderConnection;
use App\Models\ProviderResourceBinding;
use App\Services\Resources\ProviderResourceBindingService;
use App\Support\Baselines\SubjectClass;
use App\Support\Resources\ResourceIdentity;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Gate;
function providerResourceBindingAuthorizationAttributes(array $overrides = []): array
{
return array_replace([
'subject_domain' => 'baseline',
'subject_class' => SubjectClass::PolicyBacked,
'subject_type_key' => 'deviceConfiguration',
'operator_note' => 'Authorization test decision note.',
], $overrides);
}
it('allows managers to create provider resource bindings', function (): void {
[$manager, $tenant] = createUserWithTenant(role: 'manager');
$binding = app(ProviderResourceBindingService::class)->createExactProviderIdentity(
actor: $manager,
environment: $tenant,
identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'manager-allowed'),
attributes: providerResourceBindingAuthorizationAttributes(),
);
expect($binding->exists)->toBeTrue();
});
it('returns forbidden for entitled readonly users missing baseline manage capability', function (): void {
[$readonly, $tenant] = createUserWithTenant(role: 'readonly');
try {
app(ProviderResourceBindingService::class)->createManualBinding(
actor: $readonly,
environment: $tenant,
identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'readonly-denied'),
attributes: providerResourceBindingAuthorizationAttributes(),
);
$this->fail('Expected authorization denial.');
} catch (AuthorizationException $exception) {
expect($exception->status())->toBe(403);
}
});
it('returns deny-as-not-found for users outside the binding workspace', function (): void {
[$actor] = createUserWithTenant(role: 'owner');
[, $foreignTenant] = createUserWithTenant(role: 'owner');
try {
app(ProviderResourceBindingService::class)->createManualBinding(
actor: $actor,
environment: $foreignTenant,
identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'foreign-denied'),
attributes: providerResourceBindingAuthorizationAttributes(),
);
$this->fail('Expected not-found authorization denial.');
} catch (AuthorizationException $exception) {
expect($exception->status())->toBe(404);
}
});
it('rejects provider connections from another managed environment as not found', function (): void {
[$actor, $tenant] = createUserWithTenant(role: 'owner');
[, $foreignTenant] = createUserWithTenant(role: 'owner');
$foreignConnection = ProviderConnection::factory()->create([
'workspace_id' => (int) $foreignTenant->workspace_id,
'managed_environment_id' => (int) $foreignTenant->getKey(),
'provider' => 'fake-provider',
]);
expect(fn () => app(ProviderResourceBindingService::class)->createManualBinding(
actor: $actor,
environment: $tenant,
identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'foreign-connection'),
attributes: providerResourceBindingAuthorizationAttributes([
'provider_connection_id' => (int) $foreignConnection->getKey(),
]),
))->toThrow(ModelNotFoundException::class);
});
it('rejects source references from another managed environment as not found', function (): void {
[$actor, $tenant] = createUserWithTenant(role: 'owner');
[, $foreignTenant] = createUserWithTenant(role: 'owner');
$foreignInventory = InventoryItem::factory()->create([
'managed_environment_id' => (int) $foreignTenant->getKey(),
]);
expect(fn () => app(ProviderResourceBindingService::class)->createManualBinding(
actor: $actor,
environment: $tenant,
identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'foreign-source'),
attributes: providerResourceBindingAuthorizationAttributes([
'source_inventory_item_id' => (int) $foreignInventory->getKey(),
]),
))->toThrow(ModelNotFoundException::class);
});
it('rejects baseline snapshot references not assigned to the binding managed environment as not found', function (): void {
[$actor, $tenant] = createUserWithTenant(role: 'owner');
$foreignTenant = ManagedEnvironment::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
]);
$foreignProfile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
]);
$foreignSnapshot = BaselineSnapshot::factory()->complete()->create([
'workspace_id' => (int) $tenant->workspace_id,
'baseline_profile_id' => (int) $foreignProfile->getKey(),
]);
BaselineTenantAssignment::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $foreignTenant->getKey(),
'baseline_profile_id' => (int) $foreignProfile->getKey(),
]);
expect(fn () => app(ProviderResourceBindingService::class)->createManualBinding(
actor: $actor,
environment: $tenant,
identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'foreign-baseline-snapshot'),
attributes: providerResourceBindingAuthorizationAttributes([
'source_baseline_snapshot_id' => (int) $foreignSnapshot->getKey(),
]),
))->toThrow(ModelNotFoundException::class);
});
it('uses baseline capabilities in the registered policy', function (): void {
[$owner, $tenant] = createUserWithTenant(role: 'owner');
[$readonly] = createUserWithTenant(tenant: $tenant, role: 'readonly');
$binding = ProviderResourceBinding::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
]);
expect(Gate::forUser($owner)->inspect('revoke', $binding)->allowed())->toBeTrue()
->and(Gate::forUser($readonly)->inspect('view', $binding)->allowed())->toBeTrue()
->and(Gate::forUser($readonly)->inspect('revoke', $binding)->status())->toBe(403);
});