TenantAtlas/app/Providers/AppServiceProvider.php
ahmido 0dc79520a4 feat: provider access hardening (RBAC write gate) (#132)
Implements provider access hardening for Intune write operations:

- RBAC-based write gate with configurable staleness thresholds
- Gate enforced at restore start and in jobs (execute + assignments)
- UI affordances: disabled rerun action, tenant RBAC status card, refresh RBAC action
- Audit logging for blocked writes
- Ops UX label: `rbac.health_check` now displays as “RBAC health check”
- Adds/updates Pest tests and SpecKit artifacts for feature 108

Notes:
- Filament v5 / Livewire v4 compliant.
- Destructive actions require confirmation.
- Assets: no new global assets.

Tested:
- `vendor/bin/sail artisan test --compact` (suite previously green) + focused OpsUx tests for OperationCatalog labels.
- `vendor/bin/sail bin pint --dirty`.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #132
2026-02-23 00:49:37 +00:00

150 lines
5.3 KiB
PHP

<?php
namespace App\Providers;
use App\Models\BackupSchedule;
use App\Models\EntraGroup;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Models\ProviderCredential;
use App\Models\RestoreRun;
use App\Models\Tenant;
use App\Models\User;
use App\Models\UserTenantPreference;
use App\Observers\ProviderCredentialObserver;
use App\Observers\RestoreRunObserver;
use App\Policies\BackupSchedulePolicy;
use App\Policies\EntraGroupPolicy;
use App\Policies\FindingPolicy;
use App\Policies\OperationRunPolicy;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\MicrosoftGraphClient;
use App\Services\Graph\NullGraphClient;
use App\Services\Intune\AppProtectionPolicyNormalizer;
use App\Services\Intune\CompliancePolicyNormalizer;
use App\Services\Intune\DeviceConfigurationPolicyNormalizer;
use App\Services\Intune\EnrollmentAutopilotPolicyNormalizer;
use App\Services\Intune\GroupPolicyConfigurationNormalizer;
use App\Services\Intune\ManagedDeviceAppConfigurationNormalizer;
use App\Services\Intune\ScriptsPolicyNormalizer;
use App\Services\Intune\SettingsCatalogPolicyNormalizer;
use App\Services\Intune\TermsAndConditionsNormalizer;
use App\Services\Intune\WindowsDriverUpdateProfileNormalizer;
use App\Services\Intune\WindowsFeatureUpdateProfileNormalizer;
use App\Services\Intune\WindowsQualityUpdateProfileNormalizer;
use App\Services\Intune\WindowsUpdateRingNormalizer;
use App\Services\PermissionPosture\FindingGeneratorContract;
use App\Services\PermissionPosture\PermissionPostureFindingGenerator;
use App\Services\Providers\MicrosoftGraphOptionsResolver;
use App\Services\Providers\ProviderConnectionResolver;
use App\Services\Providers\ProviderGateway;
use Filament\Events\TenantSet;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
$this->app->bind(FindingGeneratorContract::class, PermissionPostureFindingGenerator::class);
$this->app->bind(
\App\Contracts\Hardening\WriteGateInterface::class,
\App\Services\Hardening\IntuneRbacWriteGate::class,
);
$this->app->singleton(GraphClientInterface::class, function ($app) {
$config = $app['config']->get('graph');
if (! empty($config['enabled'])) {
return $app->make(MicrosoftGraphClient::class);
}
return $app->make(NullGraphClient::class);
});
$this->app->singleton(MicrosoftGraphOptionsResolver::class, function ($app): MicrosoftGraphOptionsResolver {
return new MicrosoftGraphOptionsResolver(
connections: $app->make(ProviderConnectionResolver::class),
gateway: $app->make(ProviderGateway::class),
);
});
$this->app->tag(
[
AppProtectionPolicyNormalizer::class,
CompliancePolicyNormalizer::class,
DeviceConfigurationPolicyNormalizer::class,
EnrollmentAutopilotPolicyNormalizer::class,
GroupPolicyConfigurationNormalizer::class,
ManagedDeviceAppConfigurationNormalizer::class,
ScriptsPolicyNormalizer::class,
SettingsCatalogPolicyNormalizer::class,
TermsAndConditionsNormalizer::class,
WindowsDriverUpdateProfileNormalizer::class,
WindowsFeatureUpdateProfileNormalizer::class,
WindowsQualityUpdateProfileNormalizer::class,
WindowsUpdateRingNormalizer::class,
],
'policy-type-normalizers'
);
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
RateLimiter::for('entra-callback', function (Request $request) {
return Limit::perMinute(20)->by((string) $request->ip());
});
RestoreRun::observe(RestoreRunObserver::class);
ProviderCredential::observe(ProviderCredentialObserver::class);
Event::listen(TenantSet::class, function (TenantSet $event): void {
static $hasPreferencesTable;
$hasPreferencesTable ??= Schema::hasTable('user_tenant_preferences');
if (! $hasPreferencesTable) {
return;
}
$tenant = $event->getTenant();
$user = $event->getUser();
if (! $tenant instanceof Tenant) {
return;
}
if (! $user instanceof User) {
return;
}
UserTenantPreference::query()->updateOrCreate(
[
'user_id' => $user->getKey(),
'tenant_id' => $tenant->getKey(),
],
[
'last_used_at' => now(),
],
);
});
Gate::policy(BackupSchedule::class, BackupSchedulePolicy::class);
Gate::policy(Finding::class, FindingPolicy::class);
Gate::policy(EntraGroup::class, EntraGroupPolicy::class);
Gate::policy(OperationRun::class, OperationRunPolicy::class);
}
}