TenantAtlas/apps/platform/tests/Feature/ProviderResources/ProviderResourceBindingAuthorizationTest.php
Ahmed Darrazi fb2642e941
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m9s
feat(resources): implement provider resource identity binding
Added ProviderResourceBinding model, migrations, policies, and supporting framework for canonical resource identity mapping as defined in Spec 381.
2026-06-15 17:37:06 +02: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);
});