## 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
253 lines
8.2 KiB
PHP
253 lines
8.2 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\Auth\CapabilityResolver;
|
|
use App\Support\Auth\Capabilities;
|
|
use App\Support\Filament\CanonicalAdminTenantFilterState;
|
|
use Filament\Actions;
|
|
use Filament\Resources\Pages\ListRecords;
|
|
|
|
class ListProviderConnections extends ListRecords
|
|
{
|
|
protected static string $resource = ProviderConnectionResource::class;
|
|
|
|
public function mount(): void
|
|
{
|
|
app(CanonicalAdminTenantFilterState::class)->sync(
|
|
$this->getTableFiltersSessionKey(),
|
|
request: request(),
|
|
tenantFilterName: 'tenant',
|
|
tenantAttribute: 'external_id',
|
|
);
|
|
|
|
parent::mount();
|
|
}
|
|
|
|
private function tableHasRecords(): bool
|
|
{
|
|
return $this->getTableRecords()->count() > 0;
|
|
}
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
/** @var CapabilityResolver $resolver */
|
|
$resolver = app(CapabilityResolver::class);
|
|
|
|
return [
|
|
Actions\CreateAction::make()
|
|
->label('New connection')
|
|
->url(function (): string {
|
|
$tenantExternalId = $this->resolveTenantExternalIdForCreateAction();
|
|
|
|
if (! is_string($tenantExternalId) || $tenantExternalId === '') {
|
|
return ProviderConnectionResource::getUrl('create');
|
|
}
|
|
|
|
return ProviderConnectionResource::getUrl('create', [
|
|
'tenant_id' => $tenantExternalId,
|
|
]);
|
|
})
|
|
->visible(function () use ($resolver): bool {
|
|
if (! $this->tableHasRecords()) {
|
|
return false;
|
|
}
|
|
|
|
$tenant = $this->resolveTenantForCreateAction();
|
|
$user = auth()->user();
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
return true;
|
|
}
|
|
|
|
if (! $user instanceof User) {
|
|
return false;
|
|
}
|
|
|
|
return $resolver->isMember($user, $tenant);
|
|
})
|
|
->disabled(function () use ($resolver): bool {
|
|
$tenant = $this->resolveTenantForCreateAction();
|
|
$user = auth()->user();
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
return true;
|
|
}
|
|
|
|
if (! $user instanceof User) {
|
|
return true;
|
|
}
|
|
|
|
if (! $resolver->isMember($user, $tenant)) {
|
|
return true;
|
|
}
|
|
|
|
return ! $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE);
|
|
})
|
|
->tooltip(function () use ($resolver): ?string {
|
|
$tenant = $this->resolveTenantForCreateAction();
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
return 'Select a tenant to create provider connections.';
|
|
}
|
|
|
|
$user = auth()->user();
|
|
|
|
if (! $user instanceof User) {
|
|
return null;
|
|
}
|
|
|
|
if (! $resolver->isMember($user, $tenant)) {
|
|
return null;
|
|
}
|
|
|
|
if (! $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE)) {
|
|
return 'You do not have permission to create provider connections.';
|
|
}
|
|
|
|
return null;
|
|
})
|
|
->authorize(function () use ($resolver): bool {
|
|
$tenant = $this->resolveTenantForCreateAction();
|
|
$user = auth()->user();
|
|
|
|
return $tenant instanceof Tenant
|
|
&& $user instanceof User
|
|
&& $resolver->isMember($user, $tenant);
|
|
}),
|
|
];
|
|
}
|
|
|
|
private function makeEmptyStateCreateAction(): Actions\CreateAction
|
|
{
|
|
/** @var CapabilityResolver $resolver */
|
|
$resolver = app(CapabilityResolver::class);
|
|
|
|
return Actions\CreateAction::make()
|
|
->label('New connection')
|
|
->url(function (): string {
|
|
$tenantExternalId = $this->resolveTenantExternalIdForCreateAction();
|
|
|
|
if (! is_string($tenantExternalId) || $tenantExternalId === '') {
|
|
return ProviderConnectionResource::getUrl('create');
|
|
}
|
|
|
|
return ProviderConnectionResource::getUrl('create', [
|
|
'tenant_id' => $tenantExternalId,
|
|
]);
|
|
})
|
|
->visible(function () use ($resolver): bool {
|
|
$tenant = $this->resolveTenantForCreateAction();
|
|
$user = auth()->user();
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
return true;
|
|
}
|
|
|
|
if (! $user instanceof User) {
|
|
return false;
|
|
}
|
|
|
|
return $resolver->isMember($user, $tenant);
|
|
})
|
|
->disabled(function () use ($resolver): bool {
|
|
$tenant = $this->resolveTenantForCreateAction();
|
|
$user = auth()->user();
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
return true;
|
|
}
|
|
|
|
if (! $user instanceof User) {
|
|
return true;
|
|
}
|
|
|
|
if (! $resolver->isMember($user, $tenant)) {
|
|
return true;
|
|
}
|
|
|
|
return ! $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE);
|
|
})
|
|
->tooltip(function () use ($resolver): ?string {
|
|
$tenant = $this->resolveTenantForCreateAction();
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
return 'Select a tenant to create provider connections.';
|
|
}
|
|
|
|
$user = auth()->user();
|
|
|
|
if (! $user instanceof User) {
|
|
return null;
|
|
}
|
|
|
|
if (! $resolver->isMember($user, $tenant)) {
|
|
return null;
|
|
}
|
|
|
|
if (! $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE)) {
|
|
return 'You do not have permission to create provider connections.';
|
|
}
|
|
|
|
return null;
|
|
})
|
|
->authorize(function () use ($resolver): bool {
|
|
$tenant = $this->resolveTenantForCreateAction();
|
|
$user = auth()->user();
|
|
|
|
return $tenant instanceof Tenant
|
|
&& $user instanceof User
|
|
&& $resolver->isMember($user, $tenant);
|
|
});
|
|
}
|
|
|
|
private function resolveTenantExternalIdForCreateAction(): ?string
|
|
{
|
|
$filterValue = data_get($this->getTableFilterState('tenant'), 'value');
|
|
|
|
if (is_string($filterValue) && $filterValue !== '') {
|
|
return $filterValue;
|
|
}
|
|
|
|
$requested = ProviderConnectionResource::resolveRequestedTenantExternalId()
|
|
?? ProviderConnectionResource::resolveContextTenantExternalId();
|
|
|
|
if (is_string($requested) && $requested !== '') {
|
|
return $requested;
|
|
}
|
|
|
|
return ProviderConnectionResource::resolveContextTenantExternalId();
|
|
}
|
|
|
|
private function resolveTenantForCreateAction(): ?Tenant
|
|
{
|
|
$tenantExternalId = $this->resolveTenantExternalIdForCreateAction();
|
|
|
|
if (! is_string($tenantExternalId) || $tenantExternalId === '') {
|
|
return null;
|
|
}
|
|
|
|
return Tenant::query()
|
|
->where('external_id', $tenantExternalId)
|
|
->first();
|
|
}
|
|
|
|
public function getTableEmptyStateHeading(): ?string
|
|
{
|
|
return 'No Microsoft connections found';
|
|
}
|
|
|
|
public function getTableEmptyStateDescription(): ?string
|
|
{
|
|
return 'Start with a platform-managed Microsoft connection. Dedicated overrides are handled separately with stronger authorization.';
|
|
}
|
|
|
|
public function getTableEmptyStateActions(): array
|
|
{
|
|
return [$this->makeEmptyStateCreateAction()];
|
|
}
|
|
}
|