## Summary - standardize Microsoft provider connections around explicit platform vs dedicated identity modes - centralize admin-consent URL and runtime identity resolution so platform flows no longer fall back to tenant-local credentials - add migration classification, richer consent and verification state handling, dedicated override management, and focused regression coverage ## Validation - focused repo test coverage was added across provider identity, onboarding, audit, policy, guard, and migration flows - latest explicit passing run in the workspace: `vendor/bin/sail artisan test --compact tests/Feature/AdminConsentCallbackTest.php tests/Feature/Audit/ProviderConnectionConsentAuditTest.php` ## Notes - branch includes the full Spec 137 artifact set under `specs/137-platform-provider-identity/` - target base branch: `dev` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #166
250 lines
12 KiB
PHP
250 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Resources\ProviderConnectionResource\Pages;
|
|
|
|
use App\Filament\Resources\ProviderConnectionResource;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\Intune\AuditLogger;
|
|
use App\Services\Providers\ProviderConnectionMutationService;
|
|
use App\Support\Auth\Capabilities;
|
|
use App\Support\Links\RequiredPermissionsLinks;
|
|
use App\Support\Providers\ProviderConnectionType;
|
|
use App\Support\Rbac\UiEnforcement;
|
|
use Filament\Actions;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Resources\Pages\ViewRecord;
|
|
|
|
class ViewProviderConnection extends ViewRecord
|
|
{
|
|
protected static string $resource = ProviderConnectionResource::class;
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
return [
|
|
UiEnforcement::forAction(
|
|
Actions\Action::make('grant_admin_consent')
|
|
->label('Grant admin consent')
|
|
->icon('heroicon-o-clipboard-document')
|
|
->url(function (): ?string {
|
|
$tenant = ProviderConnectionResource::resolveTenantForRecord($this->record);
|
|
|
|
return $tenant instanceof Tenant
|
|
? RequiredPermissionsLinks::adminConsentPrimaryUrl($tenant)
|
|
: null;
|
|
})
|
|
->visible(function (): bool {
|
|
return ProviderConnectionResource::resolveTenantForRecord($this->record) instanceof Tenant;
|
|
})
|
|
->openUrlInNewTab()
|
|
)
|
|
->requireCapability(Capabilities::PROVIDER_MANAGE)
|
|
->apply(),
|
|
UiEnforcement::forAction(
|
|
Actions\Action::make('edit')
|
|
->label('Edit')
|
|
->icon('heroicon-o-pencil-square')
|
|
->url(fn (): string => ProviderConnectionResource::getUrl('edit', ['record' => $this->record]))
|
|
)
|
|
->requireCapability(Capabilities::PROVIDER_MANAGE)
|
|
->apply(),
|
|
Actions\ActionGroup::make([
|
|
UiEnforcement::forAction(
|
|
Actions\Action::make('enable_dedicated_override')
|
|
->label('Enable dedicated override')
|
|
->icon('heroicon-o-key')
|
|
->color('primary')
|
|
->requiresConfirmation()
|
|
->modalDescription('Dedicated credentials are stored encrypted and reset consent to the dedicated app registration.')
|
|
->visible(fn (): bool => $this->record->connection_type !== ProviderConnectionType::Dedicated)
|
|
->form([
|
|
TextInput::make('client_id')
|
|
->label('Dedicated app (client) ID')
|
|
->required()
|
|
->maxLength(255),
|
|
TextInput::make('client_secret')
|
|
->label('Dedicated client secret')
|
|
->password()
|
|
->required()
|
|
->maxLength(255),
|
|
])
|
|
->action(function (array $data, ProviderConnectionMutationService $mutations, AuditLogger $auditLogger): void {
|
|
$tenant = ProviderConnectionResource::resolveTenantForRecord($this->record);
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
abort(404);
|
|
}
|
|
|
|
$mutations->enableDedicatedOverride(
|
|
connection: $this->record,
|
|
clientId: (string) $data['client_id'],
|
|
clientSecret: (string) $data['client_secret'],
|
|
);
|
|
|
|
$user = auth()->user();
|
|
$actorId = $user instanceof User ? (int) $user->getKey() : null;
|
|
$actorEmail = $user instanceof User ? $user->email : null;
|
|
$actorName = $user instanceof User ? $user->name : null;
|
|
|
|
$auditLogger->log(
|
|
tenant: $tenant,
|
|
action: 'provider_connection.connection_type_changed',
|
|
context: [
|
|
'metadata' => [
|
|
'provider_connection_id' => (int) $this->record->getKey(),
|
|
'provider' => $this->record->provider,
|
|
'entra_tenant_id' => $this->record->entra_tenant_id,
|
|
'from_connection_type' => ProviderConnectionType::Platform->value,
|
|
'to_connection_type' => ProviderConnectionType::Dedicated->value,
|
|
'client_id' => (string) $data['client_id'],
|
|
'source' => 'provider_connection.view_page',
|
|
],
|
|
],
|
|
actorId: $actorId,
|
|
actorEmail: $actorEmail,
|
|
actorName: $actorName,
|
|
resourceType: 'provider_connection',
|
|
resourceId: (string) $this->record->getKey(),
|
|
status: 'success',
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Dedicated override enabled')
|
|
->success()
|
|
->send();
|
|
})
|
|
)
|
|
->requireCapability(Capabilities::PROVIDER_MANAGE_DEDICATED)
|
|
->preserveVisibility()
|
|
->apply(),
|
|
UiEnforcement::forAction(
|
|
Actions\Action::make('rotate_dedicated_credential')
|
|
->label('Rotate dedicated credential')
|
|
->icon('heroicon-o-arrow-path')
|
|
->color('primary')
|
|
->requiresConfirmation()
|
|
->visible(fn (): bool => $this->record->connection_type === ProviderConnectionType::Dedicated)
|
|
->form([
|
|
TextInput::make('client_id')
|
|
->label('Dedicated app (client) ID')
|
|
->default(function (): string {
|
|
$payload = $this->record->credential?->payload;
|
|
|
|
return is_array($payload) ? (string) ($payload['client_id'] ?? '') : '';
|
|
})
|
|
->required()
|
|
->maxLength(255),
|
|
TextInput::make('client_secret')
|
|
->label('Dedicated client secret')
|
|
->password()
|
|
->required()
|
|
->maxLength(255),
|
|
])
|
|
->action(function (array $data, ProviderConnectionMutationService $mutations): void {
|
|
$tenant = ProviderConnectionResource::resolveTenantForRecord($this->record);
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
abort(404);
|
|
}
|
|
|
|
$mutations->enableDedicatedOverride(
|
|
connection: $this->record,
|
|
clientId: (string) $data['client_id'],
|
|
clientSecret: (string) $data['client_secret'],
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Dedicated credential rotated')
|
|
->success()
|
|
->send();
|
|
})
|
|
)
|
|
->requireCapability(Capabilities::PROVIDER_MANAGE_DEDICATED)
|
|
->preserveVisibility()
|
|
->apply(),
|
|
UiEnforcement::forAction(
|
|
Actions\Action::make('delete_dedicated_credential')
|
|
->label('Delete dedicated credential')
|
|
->icon('heroicon-o-trash')
|
|
->color('danger')
|
|
->requiresConfirmation()
|
|
->visible(fn (): bool => $this->record->connection_type === ProviderConnectionType::Dedicated
|
|
&& $this->record->credential()->exists())
|
|
->action(function (ProviderConnectionMutationService $mutations): void {
|
|
$tenant = ProviderConnectionResource::resolveTenantForRecord($this->record);
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
abort(404);
|
|
}
|
|
|
|
$mutations->deleteDedicatedCredential($this->record);
|
|
|
|
Notification::make()
|
|
->title('Dedicated credential deleted')
|
|
->warning()
|
|
->send();
|
|
})
|
|
)
|
|
->requireCapability(Capabilities::PROVIDER_MANAGE_DEDICATED)
|
|
->preserveVisibility()
|
|
->apply(),
|
|
UiEnforcement::forAction(
|
|
Actions\Action::make('revert_to_platform')
|
|
->label('Revert to platform')
|
|
->icon('heroicon-o-arrow-uturn-left')
|
|
->color('gray')
|
|
->requiresConfirmation()
|
|
->visible(fn (): bool => $this->record->connection_type === ProviderConnectionType::Dedicated)
|
|
->action(function (ProviderConnectionMutationService $mutations, AuditLogger $auditLogger): void {
|
|
$tenant = ProviderConnectionResource::resolveTenantForRecord($this->record);
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
abort(404);
|
|
}
|
|
|
|
$mutations->revertToPlatform($this->record);
|
|
|
|
$user = auth()->user();
|
|
$actorId = $user instanceof User ? (int) $user->getKey() : null;
|
|
$actorEmail = $user instanceof User ? $user->email : null;
|
|
$actorName = $user instanceof User ? $user->name : null;
|
|
|
|
$auditLogger->log(
|
|
tenant: $tenant,
|
|
action: 'provider_connection.connection_type_changed',
|
|
context: [
|
|
'metadata' => [
|
|
'provider_connection_id' => (int) $this->record->getKey(),
|
|
'provider' => $this->record->provider,
|
|
'entra_tenant_id' => $this->record->entra_tenant_id,
|
|
'from_connection_type' => ProviderConnectionType::Dedicated->value,
|
|
'to_connection_type' => ProviderConnectionType::Platform->value,
|
|
'source' => 'provider_connection.view_page',
|
|
],
|
|
],
|
|
actorId: $actorId,
|
|
actorEmail: $actorEmail,
|
|
actorName: $actorName,
|
|
resourceType: 'provider_connection',
|
|
resourceId: (string) $this->record->getKey(),
|
|
status: 'success',
|
|
);
|
|
|
|
Notification::make()
|
|
->title('Connection reverted to platform')
|
|
->success()
|
|
->send();
|
|
})
|
|
)
|
|
->requireCapability(Capabilities::PROVIDER_MANAGE_DEDICATED)
|
|
->preserveVisibility()
|
|
->apply(),
|
|
])
|
|
->label('Manage dedicated override')
|
|
->icon('heroicon-o-cog-6-tooth')
|
|
->color('gray'),
|
|
];
|
|
}
|
|
}
|