merge: agent session work
This commit is contained in:
commit
e76ef02fab
@ -347,7 +347,7 @@ public function content(Schema $schema): Schema
|
|||||||
SchemaActions::make([
|
SchemaActions::make([
|
||||||
Action::make('wizardStartVerification')
|
Action::make('wizardStartVerification')
|
||||||
->label('Start verification')
|
->label('Start verification')
|
||||||
->visible(fn (): bool => $this->managedTenant instanceof Tenant)
|
->visible(fn (): bool => $this->managedTenant instanceof Tenant && ! $this->verificationRunIsActive())
|
||||||
->disabled(fn (): bool => ! $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START))
|
->disabled(fn (): bool => ! $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START))
|
||||||
->tooltip(fn (): ?string => $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START)
|
->tooltip(fn (): ?string => $this->currentUserCan(Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START)
|
||||||
? null
|
? null
|
||||||
@ -662,7 +662,7 @@ private function verificationStatus(): string
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($reasonCode, ['provider_auth_failed', 'permission_denied', 'provider_consent_missing'], true)) {
|
if (in_array($reasonCode, ['provider_auth_failed', 'provider_permission_denied', 'permission_denied', 'provider_consent_missing'], true)) {
|
||||||
return 'blocked';
|
return 'blocked';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,6 @@
|
|||||||
use App\Support\OpsUx\OpsUxBrowserEvents;
|
use App\Support\OpsUx\OpsUxBrowserEvents;
|
||||||
use App\Support\Rbac\UiEnforcement;
|
use App\Support\Rbac\UiEnforcement;
|
||||||
use BackedEnum;
|
use BackedEnum;
|
||||||
use Carbon\CarbonImmutable;
|
|
||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
@ -50,7 +49,6 @@
|
|||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\UniqueConstraintViolationException;
|
|
||||||
use Illuminate\Support\Facades\Bus;
|
use Illuminate\Support\Facades\Bus;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
@ -58,6 +56,10 @@
|
|||||||
|
|
||||||
class BackupScheduleResource extends Resource
|
class BackupScheduleResource extends Resource
|
||||||
{
|
{
|
||||||
|
protected static ?string $model = BackupSchedule::class;
|
||||||
|
|
||||||
|
protected static ?string $tenantOwnershipRelationshipName = 'tenant';
|
||||||
|
|
||||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-clock';
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-clock';
|
||||||
|
|
||||||
protected static string|UnitEnum|null $navigationGroup = 'Backups & Restore';
|
protected static string|UnitEnum|null $navigationGroup = 'Backups & Restore';
|
||||||
|
|||||||
@ -8,6 +8,10 @@
|
|||||||
use App\Support\Badges\BadgeRenderer;
|
use App\Support\Badges\BadgeRenderer;
|
||||||
use App\Support\OperationCatalog;
|
use App\Support\OperationCatalog;
|
||||||
use App\Support\OperationRunLinks;
|
use App\Support\OperationRunLinks;
|
||||||
|
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
|
||||||
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
|
||||||
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
|
||||||
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
|
||||||
use Filament\Actions;
|
use Filament\Actions;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
@ -20,6 +24,16 @@ class BackupScheduleOperationRunsRelationManager extends RelationManager
|
|||||||
|
|
||||||
protected static ?string $title = 'Executions';
|
protected static ?string $title = 'Executions';
|
||||||
|
|
||||||
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
||||||
|
{
|
||||||
|
return ActionSurfaceDeclaration::forRelationManager(ActionSurfaceProfile::RelationManager)
|
||||||
|
->exempt(ActionSurfaceSlot::ListHeader, 'Executions relation list has no header actions.')
|
||||||
|
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ViewAction->value)
|
||||||
|
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'Single primary row action is exposed, so no row menu is needed.')
|
||||||
|
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Bulk actions are intentionally omitted for operation run safety.')
|
||||||
|
->exempt(ActionSurfaceSlot::ListEmptyState, 'No empty-state actions are exposed in this embedded relation manager.');
|
||||||
|
}
|
||||||
|
|
||||||
public function table(Table $table): Table
|
public function table(Table $table): Table
|
||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
@ -32,7 +46,7 @@ public function table(Table $table): Table
|
|||||||
|
|
||||||
Tables\Columns\TextColumn::make('type')
|
Tables\Columns\TextColumn::make('type')
|
||||||
->label('Type')
|
->label('Type')
|
||||||
->formatStateUsing(fn (string $state): string => OperationCatalog::label($state)),
|
->formatStateUsing([OperationCatalog::class, 'label']),
|
||||||
|
|
||||||
Tables\Columns\TextColumn::make('status')
|
Tables\Columns\TextColumn::make('status')
|
||||||
->badge()
|
->badge()
|
||||||
|
|||||||
@ -43,6 +43,8 @@ class BackupSetResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = BackupSet::class;
|
protected static ?string $model = BackupSet::class;
|
||||||
|
|
||||||
|
protected static ?string $tenantOwnershipRelationshipName = 'tenant';
|
||||||
|
|
||||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-archive-box';
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-archive-box';
|
||||||
|
|
||||||
protected static string|UnitEnum|null $navigationGroup = 'Backups & Restore';
|
protected static string|UnitEnum|null $navigationGroup = 'Backups & Restore';
|
||||||
|
|||||||
@ -38,6 +38,8 @@ class FindingResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Finding::class;
|
protected static ?string $model = Finding::class;
|
||||||
|
|
||||||
|
protected static ?string $tenantOwnershipRelationshipName = 'tenant';
|
||||||
|
|
||||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-exclamation-triangle';
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-exclamation-triangle';
|
||||||
|
|
||||||
protected static string|UnitEnum|null $navigationGroup = 'Drift';
|
protected static string|UnitEnum|null $navigationGroup = 'Drift';
|
||||||
|
|||||||
@ -37,6 +37,8 @@ class InventoryItemResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = InventoryItem::class;
|
protected static ?string $model = InventoryItem::class;
|
||||||
|
|
||||||
|
protected static ?string $tenantOwnershipRelationshipName = 'tenant';
|
||||||
|
|
||||||
protected static ?string $cluster = InventoryCluster::class;
|
protected static ?string $cluster = InventoryCluster::class;
|
||||||
|
|
||||||
protected static ?int $navigationSort = 1;
|
protected static ?int $navigationSort = 1;
|
||||||
|
|||||||
@ -32,6 +32,8 @@ class InventorySyncRunResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = InventorySyncRun::class;
|
protected static ?string $model = InventorySyncRun::class;
|
||||||
|
|
||||||
|
protected static ?string $tenantOwnershipRelationshipName = 'tenant';
|
||||||
|
|
||||||
protected static bool $shouldRegisterNavigation = true;
|
protected static bool $shouldRegisterNavigation = true;
|
||||||
|
|
||||||
protected static ?string $cluster = InventoryCluster::class;
|
protected static ?string $cluster = InventoryCluster::class;
|
||||||
|
|||||||
@ -52,6 +52,8 @@ class PolicyResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = Policy::class;
|
protected static ?string $model = Policy::class;
|
||||||
|
|
||||||
|
protected static ?string $tenantOwnershipRelationshipName = 'tenant';
|
||||||
|
|
||||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-shield-check';
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-shield-check';
|
||||||
|
|
||||||
protected static string|UnitEnum|null $navigationGroup = 'Inventory';
|
protected static string|UnitEnum|null $navigationGroup = 'Inventory';
|
||||||
|
|||||||
@ -51,6 +51,8 @@ class PolicyVersionResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = PolicyVersion::class;
|
protected static ?string $model = PolicyVersion::class;
|
||||||
|
|
||||||
|
protected static ?string $tenantOwnershipRelationshipName = 'tenant';
|
||||||
|
|
||||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-list-bullet';
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-list-bullet';
|
||||||
|
|
||||||
protected static string|UnitEnum|null $navigationGroup = 'Inventory';
|
protected static string|UnitEnum|null $navigationGroup = 'Inventory';
|
||||||
|
|||||||
@ -18,8 +18,8 @@
|
|||||||
use App\Support\Badges\BadgeDomain;
|
use App\Support\Badges\BadgeDomain;
|
||||||
use App\Support\Badges\BadgeRenderer;
|
use App\Support\Badges\BadgeRenderer;
|
||||||
use App\Support\OperationRunLinks;
|
use App\Support\OperationRunLinks;
|
||||||
use App\Support\Rbac\UiEnforcement;
|
|
||||||
use App\Support\Providers\ProviderReasonCodes;
|
use App\Support\Providers\ProviderReasonCodes;
|
||||||
|
use App\Support\Rbac\UiEnforcement;
|
||||||
use App\Support\Workspaces\WorkspaceContext;
|
use App\Support\Workspaces\WorkspaceContext;
|
||||||
use BackedEnum;
|
use BackedEnum;
|
||||||
use Filament\Actions;
|
use Filament\Actions;
|
||||||
@ -93,7 +93,9 @@ protected static function resolveScopedTenant(): ?Tenant
|
|||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Tenant::current();
|
$filamentTenant = \Filament\Facades\Filament::getTenant();
|
||||||
|
|
||||||
|
return $filamentTenant instanceof Tenant ? $filamentTenant : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function resolveTenantExternalIdFromLivewireRequest(): ?string
|
private static function resolveTenantExternalIdFromLivewireRequest(): ?string
|
||||||
@ -772,9 +774,13 @@ public static function getEloquentQuery(): Builder
|
|||||||
return $query->whereRaw('1 = 0');
|
return $query->whereRaw('1 = 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($tenantId === null) {
|
||||||
|
return $query->whereRaw('1 = 0');
|
||||||
|
}
|
||||||
|
|
||||||
return $query
|
return $query
|
||||||
->where('workspace_id', (int) $workspaceId)
|
->where('workspace_id', (int) $workspaceId)
|
||||||
->when($tenantId, fn (Builder $query) => $query->where('tenant_id', $tenantId))
|
->where('tenant_id', $tenantId)
|
||||||
->latest('id');
|
->latest('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -792,6 +798,10 @@ public static function getPages(): array
|
|||||||
*/
|
*/
|
||||||
public static function getUrl(?string $name = null, array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null, bool $shouldGuessMissingParameters = false): string
|
public static function getUrl(?string $name = null, array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null, bool $shouldGuessMissingParameters = false): string
|
||||||
{
|
{
|
||||||
|
if (array_key_exists('tenant', $parameters) && blank($parameters['tenant'])) {
|
||||||
|
unset($parameters['tenant']);
|
||||||
|
}
|
||||||
|
|
||||||
if (! array_key_exists('tenant', $parameters)) {
|
if (! array_key_exists('tenant', $parameters)) {
|
||||||
if ($tenant instanceof Tenant) {
|
if ($tenant instanceof Tenant) {
|
||||||
$parameters['tenant'] = $tenant->external_id;
|
$parameters['tenant'] = $tenant->external_id;
|
||||||
@ -802,6 +812,20 @@ public static function getUrl(?string $name = null, array $parameters = [], bool
|
|||||||
if (! array_key_exists('tenant', $parameters) && $resolvedTenant instanceof Tenant) {
|
if (! array_key_exists('tenant', $parameters) && $resolvedTenant instanceof Tenant) {
|
||||||
$parameters['tenant'] = $resolvedTenant->external_id;
|
$parameters['tenant'] = $resolvedTenant->external_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$record = $parameters['record'] ?? null;
|
||||||
|
|
||||||
|
if (! array_key_exists('tenant', $parameters) && $record instanceof ProviderConnection) {
|
||||||
|
$recordTenant = $record->tenant;
|
||||||
|
|
||||||
|
if (! $recordTenant instanceof Tenant) {
|
||||||
|
$recordTenant = Tenant::query()->whereKey($record->tenant_id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($recordTenant instanceof Tenant) {
|
||||||
|
$parameters['tenant'] = $recordTenant->external_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$panel ??= 'admin';
|
$panel ??= 'admin';
|
||||||
|
|||||||
@ -630,9 +630,9 @@ protected function getHeaderActions(): array
|
|||||||
$hadCredentials = $record->credential()->exists();
|
$hadCredentials = $record->credential()->exists();
|
||||||
$previousStatus = (string) $record->status;
|
$previousStatus = (string) $record->status;
|
||||||
|
|
||||||
$status = $hadCredentials ? 'connected' : 'error';
|
$status = $hadCredentials ? 'connected' : 'needs_consent';
|
||||||
$errorReasonCode = $hadCredentials ? null : ProviderReasonCodes::ProviderCredentialMissing;
|
$errorReasonCode = null;
|
||||||
$errorMessage = $hadCredentials ? null : 'Provider connection credentials are missing.';
|
$errorMessage = null;
|
||||||
|
|
||||||
$record->update([
|
$record->update([
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
@ -669,8 +669,8 @@ protected function getHeaderActions(): array
|
|||||||
|
|
||||||
if (! $hadCredentials) {
|
if (! $hadCredentials) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Connection enabled (credentials missing)')
|
->title('Connection enabled (needs consent)')
|
||||||
->body('Add credentials before running checks or operations.')
|
->body('Grant admin consent before running checks or operations.')
|
||||||
->warning()
|
->warning()
|
||||||
->send();
|
->send();
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Actions\BulkAction;
|
use Filament\Actions\BulkAction;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Facades\Filament;
|
||||||
use Filament\Forms;
|
use Filament\Forms;
|
||||||
use Filament\Infolists;
|
use Filament\Infolists;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
@ -60,6 +61,17 @@ class RestoreRunResource extends Resource
|
|||||||
{
|
{
|
||||||
protected static ?string $model = RestoreRun::class;
|
protected static ?string $model = RestoreRun::class;
|
||||||
|
|
||||||
|
protected static ?string $tenantOwnershipRelationshipName = 'tenant';
|
||||||
|
|
||||||
|
public static function shouldRegisterNavigation(): bool
|
||||||
|
{
|
||||||
|
if (Filament::getCurrentPanel()?->getId() === 'admin') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::shouldRegisterNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-arrow-path-rounded-square';
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-arrow-path-rounded-square';
|
||||||
|
|
||||||
protected static string|UnitEnum|null $navigationGroup = 'Backups & Restore';
|
protected static string|UnitEnum|null $navigationGroup = 'Backups & Restore';
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
use App\Http\Controllers\RbacDelegatedAuthController;
|
use App\Http\Controllers\RbacDelegatedAuthController;
|
||||||
use App\Jobs\BulkTenantSyncJob;
|
use App\Jobs\BulkTenantSyncJob;
|
||||||
use App\Jobs\SyncPoliciesJob;
|
use App\Jobs\SyncPoliciesJob;
|
||||||
use App\Jobs\SyncRoleDefinitionsJob;
|
|
||||||
use App\Models\EntraGroup;
|
use App\Models\EntraGroup;
|
||||||
use App\Models\EntraRoleDefinition;
|
use App\Models\EntraRoleDefinition;
|
||||||
use App\Models\ProviderConnection;
|
use App\Models\ProviderConnection;
|
||||||
@ -18,6 +17,7 @@
|
|||||||
use App\Services\Auth\RoleCapabilityMap;
|
use App\Services\Auth\RoleCapabilityMap;
|
||||||
use App\Services\Directory\EntraGroupLabelResolver;
|
use App\Services\Directory\EntraGroupLabelResolver;
|
||||||
use App\Services\Directory\RoleDefinitionsSyncService;
|
use App\Services\Directory\RoleDefinitionsSyncService;
|
||||||
|
use App\Services\Graph\GraphClientInterface;
|
||||||
use App\Services\Intune\AuditLogger;
|
use App\Services\Intune\AuditLogger;
|
||||||
use App\Services\Intune\RbacOnboardingService;
|
use App\Services\Intune\RbacOnboardingService;
|
||||||
use App\Services\OperationRunService;
|
use App\Services\OperationRunService;
|
||||||
@ -50,9 +50,7 @@
|
|||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Throwable;
|
|
||||||
use UnitEnum;
|
use UnitEnum;
|
||||||
|
|
||||||
class TenantResource extends Resource
|
class TenantResource extends Resource
|
||||||
@ -982,6 +980,7 @@ public static function rbacAction(): Actions\Action
|
|||||||
->placeholder('Search security groups')
|
->placeholder('Search security groups')
|
||||||
->visible(fn (Get $get) => $get('scope') === 'scope_group')
|
->visible(fn (Get $get) => $get('scope') === 'scope_group')
|
||||||
->required(fn (Get $get) => $get('scope') === 'scope_group')
|
->required(fn (Get $get) => $get('scope') === 'scope_group')
|
||||||
|
->disabled(fn (?Tenant $record): bool => static::delegatedToken($record) === null)
|
||||||
->getSearchResultsUsing(fn (string $search, ?Tenant $record) => static::groupSearchOptions($record, $search))
|
->getSearchResultsUsing(fn (string $search, ?Tenant $record) => static::groupSearchOptions($record, $search))
|
||||||
->getOptionLabelUsing(fn (?string $value, ?Tenant $record) => static::groupLabelFromCache($record, $value))
|
->getOptionLabelUsing(fn (?string $value, ?Tenant $record) => static::groupLabelFromCache($record, $value))
|
||||||
->noSearchResultsMessage('No security groups found')
|
->noSearchResultsMessage('No security groups found')
|
||||||
@ -1008,6 +1007,7 @@ public static function rbacAction(): Actions\Action
|
|||||||
->placeholder('Search security groups')
|
->placeholder('Search security groups')
|
||||||
->visible(fn (Get $get) => $get('group_mode') === 'existing')
|
->visible(fn (Get $get) => $get('group_mode') === 'existing')
|
||||||
->required(fn (Get $get) => $get('group_mode') === 'existing')
|
->required(fn (Get $get) => $get('group_mode') === 'existing')
|
||||||
|
->disabled(fn (?Tenant $record): bool => static::delegatedToken($record) === null)
|
||||||
->getSearchResultsUsing(fn (string $search, ?Tenant $record) => static::groupSearchOptions($record, $search))
|
->getSearchResultsUsing(fn (string $search, ?Tenant $record) => static::groupSearchOptions($record, $search))
|
||||||
->getOptionLabelUsing(fn (?string $value, ?Tenant $record) => static::groupLabelFromCache($record, $value))
|
->getOptionLabelUsing(fn (?string $value, ?Tenant $record) => static::groupLabelFromCache($record, $value))
|
||||||
->noSearchResultsMessage('No security groups found')
|
->noSearchResultsMessage('No security groups found')
|
||||||
@ -1046,8 +1046,10 @@ public static function rbacAction(): Actions\Action
|
|||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$cacheKey = RbacDelegatedAuthController::cacheKey($record, auth()->id(), session()->getId());
|
$userCacheKey = RbacDelegatedAuthController::cacheKey($record, auth()->id(), null);
|
||||||
$token = Cache::get($cacheKey);
|
$sessionCacheKey = RbacDelegatedAuthController::cacheKey($record, auth()->id(), session()->getId());
|
||||||
|
|
||||||
|
$token = Cache::get($sessionCacheKey) ?? Cache::get($userCacheKey);
|
||||||
|
|
||||||
if (! $token) {
|
if (! $token) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
@ -1072,7 +1074,8 @@ public static function rbacAction(): Actions\Action
|
|||||||
|
|
||||||
$result = $service->run($record, $data, $user, $token);
|
$result = $service->run($record, $data, $user, $token);
|
||||||
|
|
||||||
Cache::forget($cacheKey);
|
Cache::forget($sessionCacheKey);
|
||||||
|
Cache::forget($userCacheKey);
|
||||||
|
|
||||||
if ($result['status'] === 'success') {
|
if ($result['status'] === 'success') {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
@ -1232,11 +1235,7 @@ public static function roleSearchHelper(?Tenant $tenant): ?string
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$exists = EntraRoleDefinition::query()
|
return static::delegatedToken($tenant) === null ? 'Login to search roles' : null;
|
||||||
->where('tenant_id', $tenant->getKey())
|
|
||||||
->exists();
|
|
||||||
|
|
||||||
return $exists ? null : 'Role definitions not synced yet. Use “Sync now” to load.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1244,9 +1243,61 @@ public static function roleSearchHelper(?Tenant $tenant): ?string
|
|||||||
*/
|
*/
|
||||||
public static function roleSearchOptions(?Tenant $tenant, string $search): array
|
public static function roleSearchOptions(?Tenant $tenant, string $search): array
|
||||||
{
|
{
|
||||||
|
$token = static::delegatedToken($tenant);
|
||||||
|
|
||||||
|
if ($token !== null) {
|
||||||
|
return static::searchRoleDefinitionsDelegated($tenant, $search, $token);
|
||||||
|
}
|
||||||
|
|
||||||
return static::searchRoleDefinitions($tenant, $search);
|
return static::searchRoleDefinitions($tenant, $search);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
private static function searchRoleDefinitionsDelegated(?Tenant $tenant, string $search, string $token): array
|
||||||
|
{
|
||||||
|
if (! $tenant || mb_strlen($search) < 2) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$needle = mb_strtolower($search);
|
||||||
|
|
||||||
|
/** @var GraphClientInterface $graph */
|
||||||
|
$graph = app(GraphClientInterface::class);
|
||||||
|
|
||||||
|
$response = $graph->request('GET', 'deviceManagement/roleDefinitions', [
|
||||||
|
'access_token' => $token,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response->failed()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$roles = is_array($response->data['value'] ?? null) ? $response->data['value'] : [];
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
foreach ($roles as $role) {
|
||||||
|
$id = is_string($role['id'] ?? null) ? (string) $role['id'] : null;
|
||||||
|
$displayName = is_string($role['displayName'] ?? null) ? (string) $role['displayName'] : null;
|
||||||
|
|
||||||
|
if (! $id || ! $displayName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! str_contains(mb_strtolower($displayName), $needle)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$results[$id] = static::formatRoleLabel($displayName, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($results);
|
||||||
|
|
||||||
|
return array_slice($results, 0, 20, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, string>
|
* @return array<string, string>
|
||||||
*/
|
*/
|
||||||
@ -1314,9 +1365,13 @@ private static function loginToSearchGroupsAction(?Tenant $tenant): ?Actions\Act
|
|||||||
|
|
||||||
public static function groupSearchHelper(?Tenant $tenant): ?string
|
public static function groupSearchHelper(?Tenant $tenant): ?string
|
||||||
{
|
{
|
||||||
|
if (! $tenant) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return static::delegatedToken($tenant) === null ? 'Login to search groups' : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, string>
|
* @return array<string, string>
|
||||||
*/
|
*/
|
||||||
@ -1326,6 +1381,42 @@ public static function groupSearchOptions(?Tenant $tenant, string $search): arra
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$token = static::delegatedToken($tenant);
|
||||||
|
|
||||||
|
if ($token !== null) {
|
||||||
|
/** @var GraphClientInterface $graph */
|
||||||
|
$graph = app(GraphClientInterface::class);
|
||||||
|
|
||||||
|
$response = $graph->request('GET', 'groups', [
|
||||||
|
'access_token' => $token,
|
||||||
|
'query' => [
|
||||||
|
'$filter' => sprintf("startswith(displayName,'%s') and securityEnabled eq true", addslashes($search)),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response->failed()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = is_array($response->data['value'] ?? null) ? $response->data['value'] : [];
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
foreach ($groups as $group) {
|
||||||
|
$id = is_string($group['id'] ?? null) ? (string) $group['id'] : null;
|
||||||
|
$displayName = is_string($group['displayName'] ?? null) ? (string) $group['displayName'] : null;
|
||||||
|
|
||||||
|
if (! $id || ! $displayName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$results[$id] = EntraGroupLabelResolver::formatLabel($displayName, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($results);
|
||||||
|
|
||||||
|
return array_slice($results, 0, 20, true);
|
||||||
|
}
|
||||||
|
|
||||||
$needle = mb_strtolower($search);
|
$needle = mb_strtolower($search);
|
||||||
|
|
||||||
return EntraGroup::query()
|
return EntraGroup::query()
|
||||||
|
|||||||
@ -46,7 +46,7 @@ public function handle(PolicySyncService $service, OperationRunService $operatio
|
|||||||
{
|
{
|
||||||
$graph = app(GraphClientInterface::class);
|
$graph = app(GraphClientInterface::class);
|
||||||
|
|
||||||
if (! config('graph.enabled') || $graph instanceof NullGraphClient) {
|
if ($graph instanceof NullGraphClient) {
|
||||||
if ($this->operationRun) {
|
if ($this->operationRun) {
|
||||||
$operationRunService->updateRun(
|
$operationRunService->updateRun(
|
||||||
$this->operationRun,
|
$this->operationRun,
|
||||||
|
|||||||
@ -5,8 +5,11 @@
|
|||||||
use App\Filament\Pages\Auth\Login;
|
use App\Filament\Pages\Auth\Login;
|
||||||
use App\Filament\Pages\ChooseTenant;
|
use App\Filament\Pages\ChooseTenant;
|
||||||
use App\Filament\Pages\ChooseWorkspace;
|
use App\Filament\Pages\ChooseWorkspace;
|
||||||
|
use App\Filament\Pages\InventoryCoverage;
|
||||||
use App\Filament\Pages\NoAccess;
|
use App\Filament\Pages\NoAccess;
|
||||||
use App\Filament\Pages\TenantRequiredPermissions;
|
use App\Filament\Pages\TenantRequiredPermissions;
|
||||||
|
use App\Filament\Resources\InventoryItemResource;
|
||||||
|
use App\Filament\Resources\PolicyResource;
|
||||||
use App\Filament\Resources\ProviderConnectionResource;
|
use App\Filament\Resources\ProviderConnectionResource;
|
||||||
use App\Filament\Resources\TenantResource;
|
use App\Filament\Resources\TenantResource;
|
||||||
use App\Filament\Resources\Workspaces\WorkspaceResource;
|
use App\Filament\Resources\Workspaces\WorkspaceResource;
|
||||||
@ -37,6 +40,7 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
public function panel(Panel $panel): Panel
|
public function panel(Panel $panel): Panel
|
||||||
{
|
{
|
||||||
$panel = $panel
|
$panel = $panel
|
||||||
|
->default()
|
||||||
->id('admin')
|
->id('admin')
|
||||||
->path('admin')
|
->path('admin')
|
||||||
->login(Login::class)
|
->login(Login::class)
|
||||||
@ -44,8 +48,6 @@ public function panel(Panel $panel): Panel
|
|||||||
ChooseWorkspace::registerRoutes($panel);
|
ChooseWorkspace::registerRoutes($panel);
|
||||||
ChooseTenant::registerRoutes($panel);
|
ChooseTenant::registerRoutes($panel);
|
||||||
NoAccess::registerRoutes($panel);
|
NoAccess::registerRoutes($panel);
|
||||||
|
|
||||||
WorkspaceResource::registerRoutes($panel);
|
|
||||||
})
|
})
|
||||||
->colors([
|
->colors([
|
||||||
'primary' => Color::Amber,
|
'primary' => Color::Amber,
|
||||||
@ -104,9 +106,15 @@ public function panel(Panel $panel): Panel
|
|||||||
)
|
)
|
||||||
->resources([
|
->resources([
|
||||||
TenantResource::class,
|
TenantResource::class,
|
||||||
|
PolicyResource::class,
|
||||||
ProviderConnectionResource::class,
|
ProviderConnectionResource::class,
|
||||||
|
InventoryItemResource::class,
|
||||||
|
WorkspaceResource::class,
|
||||||
])
|
])
|
||||||
|
->discoverClusters(in: app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters')
|
||||||
|
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||||
->pages([
|
->pages([
|
||||||
|
InventoryCoverage::class,
|
||||||
TenantRequiredPermissions::class,
|
TenantRequiredPermissions::class,
|
||||||
])
|
])
|
||||||
->widgets([
|
->widgets([
|
||||||
|
|||||||
@ -30,7 +30,6 @@ class TenantPanelProvider extends PanelProvider
|
|||||||
public function panel(Panel $panel): Panel
|
public function panel(Panel $panel): Panel
|
||||||
{
|
{
|
||||||
$panel = $panel
|
$panel = $panel
|
||||||
->default()
|
|
||||||
->id('tenant')
|
->id('tenant')
|
||||||
->path('admin/t')
|
->path('admin/t')
|
||||||
->login(Login::class)
|
->login(Login::class)
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
namespace App\Services\Inventory;
|
namespace App\Services\Inventory;
|
||||||
|
|
||||||
use App\Models\InventoryItem;
|
use App\Models\InventoryItem;
|
||||||
|
use App\Models\InventorySyncRun;
|
||||||
use App\Models\OperationRun;
|
use App\Models\OperationRun;
|
||||||
use App\Models\ProviderConnection;
|
use App\Models\ProviderConnection;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
@ -10,10 +11,14 @@
|
|||||||
use App\Services\Graph\GraphResponse;
|
use App\Services\Graph\GraphResponse;
|
||||||
use App\Services\Providers\ProviderConnectionResolver;
|
use App\Services\Providers\ProviderConnectionResolver;
|
||||||
use App\Services\Providers\ProviderGateway;
|
use App\Services\Providers\ProviderGateway;
|
||||||
|
use App\Support\OperationRunOutcome;
|
||||||
|
use App\Support\OperationRunStatus;
|
||||||
|
use App\Support\OperationRunType;
|
||||||
use App\Support\Providers\ProviderReasonCodes;
|
use App\Support\Providers\ProviderReasonCodes;
|
||||||
use Illuminate\Contracts\Cache\Lock;
|
use Illuminate\Contracts\Cache\Lock;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
@ -28,6 +33,88 @@ public function __construct(
|
|||||||
private readonly ProviderGateway $providerGateway,
|
private readonly ProviderGateway $providerGateway,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs an inventory sync immediately and persists a corresponding InventorySyncRun.
|
||||||
|
*
|
||||||
|
* This is primarily used in tests and for synchronous workflows.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $selectionPayload
|
||||||
|
*/
|
||||||
|
public function syncNow(Tenant $tenant, array $selectionPayload): InventorySyncRun
|
||||||
|
{
|
||||||
|
$computed = $this->normalizeAndHashSelection($selectionPayload);
|
||||||
|
$normalizedSelection = $computed['selection'];
|
||||||
|
$selectionHash = $computed['selection_hash'];
|
||||||
|
|
||||||
|
$operationRun = OperationRun::query()->create([
|
||||||
|
'workspace_id' => (int) $tenant->workspace_id,
|
||||||
|
'tenant_id' => (int) $tenant->getKey(),
|
||||||
|
'user_id' => null,
|
||||||
|
'initiator_name' => 'System',
|
||||||
|
'type' => OperationRunType::InventorySync->value,
|
||||||
|
'status' => OperationRunStatus::Running->value,
|
||||||
|
'outcome' => OperationRunOutcome::Pending->value,
|
||||||
|
'run_identity_hash' => hash('sha256', (string) $tenant->getKey().':inventory.sync:'.$selectionHash.':'.Str::uuid()->toString()),
|
||||||
|
'context' => $normalizedSelection,
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$run = InventorySyncRun::query()->create([
|
||||||
|
'tenant_id' => (int) $tenant->getKey(),
|
||||||
|
'user_id' => null,
|
||||||
|
'operation_run_id' => (int) $operationRun->getKey(),
|
||||||
|
'selection_hash' => $selectionHash,
|
||||||
|
'selection_payload' => $normalizedSelection,
|
||||||
|
'status' => InventorySyncRun::STATUS_RUNNING,
|
||||||
|
'had_errors' => false,
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = $this->executeSelection($operationRun, $tenant, $normalizedSelection);
|
||||||
|
|
||||||
|
$status = (string) ($result['status'] ?? InventorySyncRun::STATUS_FAILED);
|
||||||
|
$hadErrors = (bool) ($result['had_errors'] ?? true);
|
||||||
|
$errorCodes = is_array($result['error_codes'] ?? null) ? $result['error_codes'] : null;
|
||||||
|
$errorContext = is_array($result['error_context'] ?? null) ? $result['error_context'] : null;
|
||||||
|
|
||||||
|
$run->update([
|
||||||
|
'status' => $status,
|
||||||
|
'had_errors' => $hadErrors,
|
||||||
|
'error_codes' => $errorCodes,
|
||||||
|
'error_context' => $errorContext,
|
||||||
|
'items_observed_count' => (int) ($result['items_observed_count'] ?? 0),
|
||||||
|
'items_upserted_count' => (int) ($result['items_upserted_count'] ?? 0),
|
||||||
|
'errors_count' => (int) ($result['errors_count'] ?? 0),
|
||||||
|
'finished_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$policyTypes = $normalizedSelection['policy_types'] ?? [];
|
||||||
|
$policyTypes = is_array($policyTypes) ? $policyTypes : [];
|
||||||
|
|
||||||
|
$operationOutcome = match ($status) {
|
||||||
|
'success' => OperationRunOutcome::Succeeded->value,
|
||||||
|
'partial' => OperationRunOutcome::PartiallySucceeded->value,
|
||||||
|
'skipped' => OperationRunOutcome::Blocked->value,
|
||||||
|
default => OperationRunOutcome::Failed->value,
|
||||||
|
};
|
||||||
|
|
||||||
|
$operationRun->update([
|
||||||
|
'status' => OperationRunStatus::Completed->value,
|
||||||
|
'outcome' => $operationOutcome,
|
||||||
|
'summary_counts' => [
|
||||||
|
'total' => count($policyTypes),
|
||||||
|
'processed' => count($policyTypes),
|
||||||
|
'succeeded' => $status === 'success' ? count($policyTypes) : max(0, count($policyTypes) - (int) ($result['errors_count'] ?? 0)),
|
||||||
|
'failed' => (int) ($result['errors_count'] ?? 0),
|
||||||
|
'items' => (int) ($result['items_observed_count'] ?? 0),
|
||||||
|
'updated' => (int) ($result['items_upserted_count'] ?? 0),
|
||||||
|
],
|
||||||
|
'completed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $run->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs an inventory sync (inline), enforcing locks/concurrency.
|
* Runs an inventory sync (inline), enforcing locks/concurrency.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -65,7 +65,14 @@ public function handle(Request $request, Closure $next): Response
|
|||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$tenantParameter = null;
|
||||||
if ($request->route()?->hasParameter('tenant')) {
|
if ($request->route()?->hasParameter('tenant')) {
|
||||||
|
$tenantParameter = $request->route()->parameter('tenant');
|
||||||
|
} elseif (filled($request->query('tenant'))) {
|
||||||
|
$tenantParameter = $request->query('tenant');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tenantParameter !== null) {
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
@ -76,12 +83,9 @@ public function handle(Request $request, Closure $next): Response
|
|||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $panel->hasTenancy()) {
|
$tenant = $tenantParameter instanceof Tenant
|
||||||
return $next($request);
|
? $tenantParameter
|
||||||
}
|
: Tenant::query()->withTrashed()->where('external_id', (string) $tenantParameter)->first();
|
||||||
|
|
||||||
$tenantParameter = $request->route()->parameter('tenant');
|
|
||||||
$tenant = $panel->getTenant($tenantParameter);
|
|
||||||
|
|
||||||
if (! $tenant instanceof Tenant) {
|
if (! $tenant instanceof Tenant) {
|
||||||
abort(404);
|
abort(404);
|
||||||
|
|||||||
@ -21,7 +21,9 @@ public function requiredCapabilityForType(string $operationType): ?string
|
|||||||
'restore.execute' => Capabilities::TENANT_MANAGE,
|
'restore.execute' => Capabilities::TENANT_MANAGE,
|
||||||
'directory_role_definitions.sync' => Capabilities::TENANT_MANAGE,
|
'directory_role_definitions.sync' => Capabilities::TENANT_MANAGE,
|
||||||
|
|
||||||
'provider.connection.check' => Capabilities::PROVIDER_RUN,
|
// Viewing verification reports should be possible for readonly members.
|
||||||
|
// Starting verification is separately guarded by the verification service.
|
||||||
|
'provider.connection.check' => Capabilities::PROVIDER_VIEW,
|
||||||
|
|
||||||
// Keep legacy / unknown types viewable by membership+entitlement only.
|
// Keep legacy / unknown types viewable by membership+entitlement only.
|
||||||
default => null,
|
default => null,
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
</include>
|
</include>
|
||||||
</source>
|
</source>
|
||||||
<php>
|
<php>
|
||||||
<ini name="memory_limit" value="512M"/>
|
<ini name="memory_limit" value="2048M"/>
|
||||||
<env name="APP_ENV" value="testing"/>
|
<env name="APP_ENV" value="testing"/>
|
||||||
<env name="APP_KEY" value="base64:z63PQuXp3rUOQ0L4o8xp76xeakrn5X3owja1qFX3ccY="/>
|
<env name="APP_KEY" value="base64:z63PQuXp3rUOQ0L4o8xp76xeakrn5X3owja1qFX3ccY="/>
|
||||||
<env name="INTUNE_TENANT_ID" value="" force="true"/>
|
<env name="INTUNE_TENANT_ID" value="" force="true"/>
|
||||||
|
|||||||
@ -87,7 +87,7 @@
|
|||||||
|
|
||||||
<x-filament::dropdown.list>
|
<x-filament::dropdown.list>
|
||||||
<a
|
<a
|
||||||
href="{{ ChooseWorkspace::getUrl(panel: 'admin') }}"
|
href="{{ route('filament.admin.resources.workspaces.index') }}"
|
||||||
class="block px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-800"
|
class="block px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||||
>
|
>
|
||||||
Switch workspace
|
Switch workspace
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
Route::get('/admin/consent/start', TenantOnboardingController::class)
|
Route::get('/admin/consent/start', TenantOnboardingController::class)
|
||||||
->name('admin.consent.start');
|
->name('admin.consent.start');
|
||||||
// Panel root override: keep the app's workspace-first flow.
|
|
||||||
// Avoid Filament's tenancy root redirect which otherwise sends users into legacy flows.
|
// Avoid Filament's tenancy root redirect which otherwise sends users into legacy flows.
|
||||||
// when no default tenant can be resolved.
|
// when no default tenant can be resolved.
|
||||||
Route::middleware([
|
Route::middleware([
|
||||||
@ -148,6 +148,19 @@
|
|||||||
->get('/admin/operations', \App\Filament\Pages\Monitoring\Operations::class)
|
->get('/admin/operations', \App\Filament\Pages\Monitoring\Operations::class)
|
||||||
->name('admin.operations.index');
|
->name('admin.operations.index');
|
||||||
|
|
||||||
|
Route::middleware([
|
||||||
|
'web',
|
||||||
|
'panel:admin',
|
||||||
|
'ensure-correct-guard:web',
|
||||||
|
DisableBladeIconComponents::class,
|
||||||
|
DispatchServingFilamentEvent::class,
|
||||||
|
FilamentAuthenticate::class,
|
||||||
|
'ensure-workspace-selected',
|
||||||
|
'ensure-filament-tenant-selected',
|
||||||
|
])
|
||||||
|
->get('/admin/t/{tenant:external_id}/operations', fn () => redirect()->route('admin.operations.index'))
|
||||||
|
->name('admin.operations.legacy-tenant-index');
|
||||||
|
|
||||||
Route::middleware([
|
Route::middleware([
|
||||||
'web',
|
'web',
|
||||||
'panel:admin',
|
'panel:admin',
|
||||||
|
|||||||
@ -16,6 +16,8 @@
|
|||||||
]);
|
]);
|
||||||
$tenant->makeCurrent();
|
$tenant->makeCurrent();
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policies = Policy::factory()
|
$policies = Policy::factory()
|
||||||
->count(3)
|
->count(3)
|
||||||
->create([
|
->create([
|
||||||
|
|||||||
@ -53,6 +53,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
it('extracts edges during inventory sync and marks missing appropriately', function () {
|
it('extracts edges during inventory sync and marks missing appropriately', function () {
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
$this->app->bind(GraphClientInterface::class, fn () => new FakeGraphClientForDeps);
|
$this->app->bind(GraphClientInterface::class, fn () => new FakeGraphClientForDeps);
|
||||||
|
|
||||||
$svc = app(InventorySyncService::class);
|
$svc = app(InventorySyncService::class);
|
||||||
@ -73,6 +74,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
it('respects 50-edge limit for outbound extraction', function () {
|
it('respects 50-edge limit for outbound extraction', function () {
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
// Fake client returning 60 group assignments
|
// Fake client returning 60 group assignments
|
||||||
$this->app->bind(GraphClientInterface::class, function () {
|
$this->app->bind(GraphClientInterface::class, function () {
|
||||||
return new class implements GraphClientInterface
|
return new class implements GraphClientInterface
|
||||||
@ -132,6 +134,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
it('persists unsupported reference warnings on the sync run record', function () {
|
it('persists unsupported reference warnings on the sync run record', function () {
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$this->app->bind(GraphClientInterface::class, function () {
|
$this->app->bind(GraphClientInterface::class, function () {
|
||||||
return new class implements GraphClientInterface
|
return new class implements GraphClientInterface
|
||||||
@ -276,6 +279,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
it('hydrates settings catalog assignments and extracts include/exclude/filter edges', function () {
|
it('hydrates settings catalog assignments and extracts include/exclude/filter edges', function () {
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$this->app->bind(GraphClientInterface::class, function () {
|
$this->app->bind(GraphClientInterface::class, function () {
|
||||||
return new class implements GraphClientInterface
|
return new class implements GraphClientInterface
|
||||||
|
|||||||
@ -97,6 +97,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'tenant_id' => 'tenant-1',
|
'tenant_id' => 'tenant-1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::factory()->create([
|
$policy = Policy::factory()->create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'dcs-1',
|
'external_id' => 'dcs-1',
|
||||||
|
|||||||
@ -58,6 +58,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
$backupSet = BackupSet::factory()->for($tenant)->create([
|
$backupSet = BackupSet::factory()->for($tenant)->create([
|
||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
'item_count' => 1,
|
'item_count' => 1,
|
||||||
|
|||||||
@ -82,6 +82,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
$backupSet = BackupSet::factory()->for($tenant)->create([
|
$backupSet = BackupSet::factory()->for($tenant)->create([
|
||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
'item_count' => 1,
|
'item_count' => 1,
|
||||||
@ -163,6 +165,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
$backupSet = BackupSet::factory()->for($tenant)->create([
|
$backupSet = BackupSet::factory()->for($tenant)->create([
|
||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
'item_count' => 1,
|
'item_count' => 1,
|
||||||
|
|||||||
@ -57,6 +57,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'ca-policy-1',
|
'external_id' => 'ca-policy-1',
|
||||||
|
|||||||
@ -57,6 +57,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'enrollment-restriction-1',
|
'external_id' => 'enrollment-restriction-1',
|
||||||
@ -159,6 +161,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'enrollment-limit-1',
|
'external_id' => 'enrollment-limit-1',
|
||||||
|
|||||||
@ -95,6 +95,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
putenv('INTUNE_TENANT_ID='.$tenant->tenant_id);
|
putenv('INTUNE_TENANT_ID='.$tenant->tenant_id);
|
||||||
$_ENV['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
$_ENV['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
||||||
$_SERVER['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
$_SERVER['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
||||||
|
|||||||
@ -103,6 +103,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'gpo-1',
|
'external_id' => 'gpo-1',
|
||||||
|
|||||||
@ -54,6 +54,8 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
||||||
$tenant->makeCurrent();
|
$tenant->makeCurrent();
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->get(route('filament.admin.resources.policies.index', filamentTenantRouteParams($tenant)))
|
->get(route('filament.tenant.resources.policies.index', filamentTenantRouteParams($tenant)))
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('Policy A')
|
->assertSee('Policy A')
|
||||||
->assertDontSee('Policy B');
|
->assertDontSee('Policy B');
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
[$user, $tenant] = createUserWithTenant(tenant: $tenant, user: $user, role: 'owner');
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, user: $user, role: 'owner');
|
||||||
|
|
||||||
$response = $this->actingAs($user)
|
$response = $this->actingAs($user)
|
||||||
->get(PolicyResource::getUrl('view', ['record' => $policy], tenant: $tenant).'?tab=settings');
|
->get(PolicyResource::getUrl('view', ['record' => $policy]).'?tab=settings&tenant='.(string) $tenant->external_id);
|
||||||
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$response->assertSee('Settings');
|
$response->assertSee('Settings');
|
||||||
|
|||||||
@ -71,6 +71,8 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'gpo-versioned-1',
|
'external_id' => 'gpo-versioned-1',
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Filament\Resources\PolicyVersionResource\Pages\ListPolicyVersions;
|
use App\Filament\Resources\PolicyVersionResource\Pages\ListPolicyVersions;
|
||||||
use App\Filament\Resources\RestoreRunResource;
|
|
||||||
use App\Filament\Resources\RestoreRunResource\Pages\CreateRestoreRun;
|
use App\Filament\Resources\RestoreRunResource\Pages\CreateRestoreRun;
|
||||||
use App\Models\BackupItem;
|
use App\Models\BackupItem;
|
||||||
use App\Models\BackupSet;
|
use App\Models\BackupSet;
|
||||||
@ -58,7 +57,8 @@
|
|||||||
|
|
||||||
Livewire::test(ListPolicyVersions::class)
|
Livewire::test(ListPolicyVersions::class)
|
||||||
->callTableAction('restore_via_wizard', $version)
|
->callTableAction('restore_via_wizard', $version)
|
||||||
->assertRedirectContains(RestoreRunResource::getUrl('create', [], false, tenant: $tenant));
|
->assertRedirectContains('/admin/restore-runs/create')
|
||||||
|
->assertRedirectContains('tenant='.(string) $tenant->external_id);
|
||||||
|
|
||||||
$backupSet = BackupSet::query()->where('metadata->source', 'policy_version')->first();
|
$backupSet = BackupSet::query()->where('metadata->source', 'policy_version')->first();
|
||||||
expect($backupSet)->not->toBeNull();
|
expect($backupSet)->not->toBeNull();
|
||||||
|
|||||||
@ -126,7 +126,7 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->actingAs($user)
|
$response = $this->actingAs($user)
|
||||||
->get(PolicyVersionResource::getUrl('view', ['record' => $version], tenant: $tenant).'?tab=normalized-settings');
|
->get(PolicyVersionResource::getUrl('view', ['record' => $version]).'?tab=normalized-settings&tenant='.(string) $tenant->external_id);
|
||||||
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$response->assertSee('Enrollment notifications');
|
$response->assertSee('Enrollment notifications');
|
||||||
|
|||||||
@ -56,6 +56,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
'name' => 'Tenant One',
|
'name' => 'Tenant One',
|
||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
@ -148,6 +149,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
$backupSet = BackupSet::factory()->for($tenant)->create();
|
$backupSet = BackupSet::factory()->for($tenant)->create();
|
||||||
$backupItem = BackupItem::factory()
|
$backupItem = BackupItem::factory()
|
||||||
->for($tenant)
|
->for($tenant)
|
||||||
@ -251,6 +253,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
'name' => 'Tenant Three',
|
'name' => 'Tenant Three',
|
||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
@ -365,6 +368,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
@ -481,6 +485,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
'name' => 'Tenant One',
|
'name' => 'Tenant One',
|
||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$backupSet = BackupSet::factory()->for($tenant)->create([
|
$backupSet = BackupSet::factory()->for($tenant)->create([
|
||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
@ -586,6 +591,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
'name' => 'Tenant Four',
|
'name' => 'Tenant Four',
|
||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$backupSet = BackupSet::factory()->for($tenant)->create([
|
$backupSet = BackupSet::factory()->for($tenant)->create([
|
||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
|
|||||||
@ -26,7 +26,7 @@ function makeAssignment(string $odataType, string $groupId, ?string $displayName
|
|||||||
bindFailHardGraphClient();
|
bindFailHardGraphClient();
|
||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->get(RestoreRunResource::getUrl('create', tenant: $tenant))
|
->get(RestoreRunResource::getUrl('create').'?tenant='.(string) $tenant->external_id)
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('Create restore run')
|
->assertSee('Create restore run')
|
||||||
->assertSee('Select Backup Set');
|
->assertSee('Select Backup Set');
|
||||||
@ -54,7 +54,7 @@ function makeAssignment(string $odataType, string $groupId, ?string $displayName
|
|||||||
|
|
||||||
bindFailHardGraphClient();
|
bindFailHardGraphClient();
|
||||||
|
|
||||||
$url = RestoreRunResource::getUrl('create', tenant: $tenant).'?backup_set_id='.$backupSet->getKey();
|
$url = RestoreRunResource::getUrl('create').'?backup_set_id='.$backupSet->getKey().'&tenant='.(string) $tenant->external_id;
|
||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->get($url)
|
->get($url)
|
||||||
|
|||||||
@ -50,10 +50,10 @@
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->get(\App\Filament\Resources\PolicyVersionResource::getUrl('index', tenant: $tenant))
|
$this->get(\App\Filament\Resources\PolicyVersionResource::getUrl('index').'?tenant='.(string) $tenant->external_id)
|
||||||
->assertSuccessful();
|
->assertSuccessful();
|
||||||
|
|
||||||
$this->get(\App\Filament\Resources\PolicyVersionResource::getUrl('view', ['record' => $version], tenant: $tenant).'?tab=normalized-settings')
|
$this->get(\App\Filament\Resources\PolicyVersionResource::getUrl('view', ['record' => $version]).'?tab=normalized-settings&tenant='.(string) $tenant->external_id)
|
||||||
->assertSuccessful();
|
->assertSuccessful();
|
||||||
|
|
||||||
$originalEnv !== false
|
$originalEnv !== false
|
||||||
@ -116,9 +116,9 @@
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$url = \App\Filament\Resources\PolicyVersionResource::getUrl('view', ['record' => $v2], tenant: $tenant);
|
$url = \App\Filament\Resources\PolicyVersionResource::getUrl('view', ['record' => $v2]).'?tenant='.(string) $tenant->external_id;
|
||||||
|
|
||||||
$this->get($url.'?tab=diff')
|
$this->get($url.'&tab=diff')
|
||||||
->assertSuccessful()
|
->assertSuccessful()
|
||||||
->assertSeeText('Fullscreen')
|
->assertSeeText('Fullscreen')
|
||||||
->assertSeeText("- Write-Host 'one'")
|
->assertSeeText("- Write-Host 'one'")
|
||||||
@ -181,9 +181,9 @@
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$url = \App\Filament\Resources\PolicyVersionResource::getUrl('view', ['record' => $v2], tenant: $tenant);
|
$url = \App\Filament\Resources\PolicyVersionResource::getUrl('view', ['record' => $v2]).'?tenant='.(string) $tenant->external_id;
|
||||||
|
|
||||||
$this->get($url.'?tab=diff')
|
$this->get($url.'&tab=diff')
|
||||||
->assertSuccessful()
|
->assertSuccessful()
|
||||||
->assertSeeText('Fullscreen')
|
->assertSeeText('Fullscreen')
|
||||||
->assertSeeText("- Write-Host 'one'")
|
->assertSeeText("- Write-Host 'one'")
|
||||||
|
|||||||
@ -73,6 +73,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'is_current' => true,
|
'is_current' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
putenv('INTUNE_TENANT_ID='.$tenant->tenant_id);
|
putenv('INTUNE_TENANT_ID='.$tenant->tenant_id);
|
||||||
$_ENV['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
$_ENV['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
||||||
$_SERVER['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
$_SERVER['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
||||||
@ -126,6 +128,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'is_current' => true,
|
'is_current' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
putenv('INTUNE_TENANT_ID='.$tenant->tenant_id);
|
putenv('INTUNE_TENANT_ID='.$tenant->tenant_id);
|
||||||
$_ENV['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
$_ENV['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
||||||
$_SERVER['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
$_SERVER['INTUNE_TENANT_ID'] = $tenant->tenant_id;
|
||||||
|
|||||||
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
use App\Models\Policy;
|
use App\Models\Policy;
|
||||||
use App\Models\PolicyVersion;
|
use App\Models\PolicyVersion;
|
||||||
|
use App\Models\ProviderConnection;
|
||||||
|
use App\Models\ProviderCredential;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
|
use App\Models\Workspace;
|
||||||
use App\Services\Graph\GraphClientInterface;
|
use App\Services\Graph\GraphClientInterface;
|
||||||
use App\Services\Graph\GraphResponse;
|
use App\Services\Graph\GraphResponse;
|
||||||
use App\Services\Intune\PolicySyncService;
|
use App\Services\Intune\PolicySyncService;
|
||||||
@ -82,6 +85,27 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
$tenant->makeCurrent();
|
$tenant->makeCurrent();
|
||||||
expect(Tenant::current()->id)->toBe($tenant->id);
|
expect(Tenant::current()->id)->toBe($tenant->id);
|
||||||
|
|
||||||
|
$workspace = Workspace::factory()->create();
|
||||||
|
$tenant->forceFill(['workspace_id' => (int) $workspace->getKey()])->save();
|
||||||
|
|
||||||
|
$connection = ProviderConnection::factory()->create([
|
||||||
|
'tenant_id' => (int) $tenant->getKey(),
|
||||||
|
'workspace_id' => (int) $workspace->getKey(),
|
||||||
|
'provider' => 'microsoft',
|
||||||
|
'entra_tenant_id' => $tenant->tenant_id,
|
||||||
|
'is_default' => true,
|
||||||
|
'status' => 'connected',
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProviderCredential::factory()->create([
|
||||||
|
'provider_connection_id' => (int) $connection->getKey(),
|
||||||
|
'type' => 'client_secret',
|
||||||
|
'payload' => [
|
||||||
|
'client_id' => 'test-client-id',
|
||||||
|
'client_secret' => 'test-client-secret',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
app(PolicySyncService::class)->syncPolicies($tenant);
|
app(PolicySyncService::class)->syncPolicies($tenant);
|
||||||
|
|
||||||
$settingsPolicy = Policy::where('policy_type', 'settingsCatalogPolicy')->first();
|
$settingsPolicy = Policy::where('policy_type', 'settingsCatalogPolicy')->first();
|
||||||
@ -113,7 +137,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
$response = $this
|
$response = $this
|
||||||
->actingAs($user)
|
->actingAs($user)
|
||||||
->get(route('filament.admin.resources.policies.index', filamentTenantRouteParams($tenant)));
|
->get(route('filament.tenant.resources.policies.index', filamentTenantRouteParams($tenant)));
|
||||||
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$response->assertSee('Settings Catalog Policy');
|
$response->assertSee('Settings Catalog Policy');
|
||||||
|
|||||||
@ -103,6 +103,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'scp-3',
|
'external_id' => 'scp-3',
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
use App\Models\BackupItem;
|
use App\Models\BackupItem;
|
||||||
use App\Models\BackupSet;
|
use App\Models\BackupSet;
|
||||||
use App\Models\Policy;
|
use App\Models\Policy;
|
||||||
|
use App\Models\ProviderConnection;
|
||||||
|
use App\Models\ProviderCredential;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\Graph\GraphClientInterface;
|
use App\Services\Graph\GraphClientInterface;
|
||||||
@ -12,6 +14,30 @@
|
|||||||
|
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
if (! function_exists('makeTenantWithDefaultProviderConnection')) {
|
||||||
|
function makeTenantWithDefaultProviderConnection(array $attributes = []): Tenant
|
||||||
|
{
|
||||||
|
$tenant = Tenant::create(array_merge([
|
||||||
|
'tenant_id' => 'tenant-1',
|
||||||
|
'name' => 'Tenant One',
|
||||||
|
'metadata' => [],
|
||||||
|
], $attributes));
|
||||||
|
|
||||||
|
$connection = ProviderConnection::factory()->create([
|
||||||
|
'tenant_id' => $tenant->id,
|
||||||
|
'provider' => 'microsoft',
|
||||||
|
'is_default' => true,
|
||||||
|
'status' => 'ok',
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProviderCredential::factory()->create([
|
||||||
|
'provider_connection_id' => $connection->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $tenant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SettingsCatalogRestoreGraphClient implements GraphClientInterface
|
class SettingsCatalogRestoreGraphClient implements GraphClientInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -105,11 +131,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::create([
|
$tenant = makeTenantWithDefaultProviderConnection();
|
||||||
'tenant_id' => 'tenant-1',
|
|
||||||
'name' => 'Tenant One',
|
|
||||||
'metadata' => [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
@ -204,7 +226,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
->toBe('#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance');
|
->toBe('#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance');
|
||||||
|
|
||||||
$response = $this
|
$response = $this
|
||||||
->get(route('filament.admin.resources.restore-runs.view', array_merge(filamentTenantRouteParams($tenant), ['record' => $run])));
|
->get(route('filament.tenant.resources.restore-runs.view', array_merge(filamentTenantRouteParams($tenant), ['record' => $run])));
|
||||||
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$response->assertSee('settings are read-only');
|
$response->assertSee('settings are read-only');
|
||||||
@ -225,10 +247,9 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::create([
|
$tenant = makeTenantWithDefaultProviderConnection([
|
||||||
'tenant_id' => 'tenant-2',
|
'tenant_id' => 'tenant-2',
|
||||||
'name' => 'Tenant Two',
|
'name' => 'Tenant Two',
|
||||||
'metadata' => [],
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
@ -346,10 +367,9 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::create([
|
$tenant = makeTenantWithDefaultProviderConnection([
|
||||||
'tenant_id' => 'tenant-4',
|
'tenant_id' => 'tenant-4',
|
||||||
'name' => 'Tenant Four',
|
'name' => 'Tenant Four',
|
||||||
'metadata' => [],
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
@ -464,10 +484,9 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::create([
|
$tenant = makeTenantWithDefaultProviderConnection([
|
||||||
'tenant_id' => 'tenant-5',
|
'tenant_id' => 'tenant-5',
|
||||||
'name' => 'Tenant Five',
|
'name' => 'Tenant Five',
|
||||||
'metadata' => [],
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
|
|||||||
@ -56,8 +56,10 @@
|
|||||||
|
|
||||||
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
|
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
|
||||||
|
|
||||||
|
$tenant->makeCurrent();
|
||||||
|
|
||||||
$policyResponse = $this->actingAs($user)
|
$policyResponse = $this->actingAs($user)
|
||||||
->get(PolicyResource::getUrl('view', ['record' => $policy], tenant: $tenant).'?tab=settings');
|
->get(PolicyResource::getUrl('view', ['record' => $policy], panel: 'admin').'?tab=settings&tenant='.(string) $tenant->external_id);
|
||||||
|
|
||||||
$policyResponse->assertOk();
|
$policyResponse->assertOk();
|
||||||
$policyResponse->assertSee('fi-width-full');
|
$policyResponse->assertSee('fi-width-full');
|
||||||
@ -68,7 +70,7 @@
|
|||||||
$policyResponse->assertSee('fi-ta-table');
|
$policyResponse->assertSee('fi-ta-table');
|
||||||
|
|
||||||
$versionResponse = $this->actingAs($user)
|
$versionResponse = $this->actingAs($user)
|
||||||
->get(PolicyVersionResource::getUrl('view', ['record' => $version], tenant: $tenant));
|
->get(PolicyVersionResource::getUrl('view', ['record' => $version], panel: 'admin').'?tenant='.(string) $tenant->external_id);
|
||||||
|
|
||||||
$versionResponse->assertOk();
|
$versionResponse->assertOk();
|
||||||
$versionResponse->assertSee('fi-width-full');
|
$versionResponse->assertSee('fi-width-full');
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
$unauthorizedTenant = Tenant::factory()->create();
|
$unauthorizedTenant = Tenant::factory()->create();
|
||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->get(route('filament.admin.resources.policies.index', filamentTenantRouteParams($unauthorizedTenant)))
|
->get(route('filament.tenant.resources.policies.index', filamentTenantRouteParams($unauthorizedTenant)))
|
||||||
->assertNotFound();
|
->assertNotFound();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
use App\Filament\Resources\TenantResource\Pages\ViewTenant;
|
use App\Filament\Resources\TenantResource\Pages\ViewTenant;
|
||||||
use App\Http\Controllers\RbacDelegatedAuthController;
|
use App\Http\Controllers\RbacDelegatedAuthController;
|
||||||
|
use App\Models\ProviderConnection;
|
||||||
|
use App\Models\ProviderCredential;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Services\Graph\GraphClientInterface;
|
use App\Services\Graph\GraphClientInterface;
|
||||||
use App\Services\Graph\GraphResponse;
|
use App\Services\Graph\GraphResponse;
|
||||||
@ -19,13 +21,30 @@
|
|||||||
|
|
||||||
function tenantWithApp(): Tenant
|
function tenantWithApp(): Tenant
|
||||||
{
|
{
|
||||||
return Tenant::create([
|
$tenant = Tenant::create([
|
||||||
'tenant_id' => 'tenant-guid',
|
'tenant_id' => 'tenant-guid',
|
||||||
'name' => 'Tenant One',
|
'name' => 'Tenant One',
|
||||||
'app_client_id' => 'client-123',
|
'app_client_id' => 'client-123',
|
||||||
'app_client_secret' => 'secret',
|
'app_client_secret' => 'secret',
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$connection = ProviderConnection::factory()->create([
|
||||||
|
'tenant_id' => $tenant->getKey(),
|
||||||
|
'provider' => 'microsoft',
|
||||||
|
'is_default' => true,
|
||||||
|
'status' => 'ok',
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProviderCredential::factory()->create([
|
||||||
|
'provider_connection_id' => $connection->getKey(),
|
||||||
|
'payload' => [
|
||||||
|
'client_id' => 'client-123',
|
||||||
|
'client_secret' => 'secret',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $tenant;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('rbac action prompts login when no delegated token', function () {
|
test('rbac action prompts login when no delegated token', function () {
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
use App\Models\BackupSet;
|
use App\Models\BackupSet;
|
||||||
use App\Models\Policy;
|
use App\Models\Policy;
|
||||||
use App\Models\PolicyVersion;
|
use App\Models\PolicyVersion;
|
||||||
|
use App\Models\ProviderConnection;
|
||||||
|
use App\Models\ProviderCredential;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\Graph\GraphClientInterface;
|
use App\Services\Graph\GraphClientInterface;
|
||||||
@ -13,6 +15,30 @@
|
|||||||
|
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
if (! function_exists('makeTenantWithDefaultProviderConnection')) {
|
||||||
|
function makeTenantWithDefaultProviderConnection(array $attributes = []): Tenant
|
||||||
|
{
|
||||||
|
$tenant = Tenant::create(array_merge([
|
||||||
|
'tenant_id' => 'tenant-1',
|
||||||
|
'name' => 'Tenant One',
|
||||||
|
'metadata' => [],
|
||||||
|
], $attributes));
|
||||||
|
|
||||||
|
$connection = ProviderConnection::factory()->create([
|
||||||
|
'tenant_id' => $tenant->id,
|
||||||
|
'provider' => 'microsoft',
|
||||||
|
'is_default' => true,
|
||||||
|
'status' => 'ok',
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProviderCredential::factory()->create([
|
||||||
|
'provider_connection_id' => $connection->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $tenant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class WindowsUpdateProfilesRestoreGraphClient implements GraphClientInterface
|
class WindowsUpdateProfilesRestoreGraphClient implements GraphClientInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -62,11 +88,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
$client = new WindowsUpdateProfilesRestoreGraphClient;
|
$client = new WindowsUpdateProfilesRestoreGraphClient;
|
||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::create([
|
$tenant = makeTenantWithDefaultProviderConnection();
|
||||||
'tenant_id' => 'tenant-1',
|
|
||||||
'name' => 'Tenant One',
|
|
||||||
'metadata' => [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
@ -141,11 +163,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
$client = new WindowsUpdateProfilesRestoreGraphClient;
|
$client = new WindowsUpdateProfilesRestoreGraphClient;
|
||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::create([
|
$tenant = makeTenantWithDefaultProviderConnection();
|
||||||
'tenant_id' => 'tenant-1',
|
|
||||||
'name' => 'Tenant One',
|
|
||||||
'metadata' => [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
@ -220,11 +238,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
$client = new WindowsUpdateProfilesRestoreGraphClient;
|
$client = new WindowsUpdateProfilesRestoreGraphClient;
|
||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::create([
|
$tenant = makeTenantWithDefaultProviderConnection();
|
||||||
'tenant_id' => 'tenant-1',
|
|
||||||
'name' => 'Tenant One',
|
|
||||||
'metadata' => [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
use App\Models\BackupSet;
|
use App\Models\BackupSet;
|
||||||
use App\Models\Policy;
|
use App\Models\Policy;
|
||||||
use App\Models\PolicyVersion;
|
use App\Models\PolicyVersion;
|
||||||
|
use App\Models\ProviderConnection;
|
||||||
|
use App\Models\ProviderCredential;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\Graph\GraphClientInterface;
|
use App\Services\Graph\GraphClientInterface;
|
||||||
@ -13,6 +15,30 @@
|
|||||||
|
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
if (! function_exists('makeTenantWithDefaultProviderConnection')) {
|
||||||
|
function makeTenantWithDefaultProviderConnection(array $attributes = []): Tenant
|
||||||
|
{
|
||||||
|
$tenant = Tenant::create(array_merge([
|
||||||
|
'tenant_id' => 'tenant-1',
|
||||||
|
'name' => 'Tenant One',
|
||||||
|
'metadata' => [],
|
||||||
|
], $attributes));
|
||||||
|
|
||||||
|
$connection = ProviderConnection::factory()->create([
|
||||||
|
'tenant_id' => $tenant->id,
|
||||||
|
'provider' => 'microsoft',
|
||||||
|
'is_default' => true,
|
||||||
|
'status' => 'ok',
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProviderCredential::factory()->create([
|
||||||
|
'provider_connection_id' => $connection->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $tenant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test('restore execution applies windows update ring and records audit log', function () {
|
test('restore execution applies windows update ring and records audit log', function () {
|
||||||
$client = new class implements GraphClientInterface
|
$client = new class implements GraphClientInterface
|
||||||
{
|
{
|
||||||
@ -66,11 +92,7 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon
|
|||||||
|
|
||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::create([
|
$tenant = makeTenantWithDefaultProviderConnection();
|
||||||
'tenant_id' => 'tenant-1',
|
|
||||||
'name' => 'Tenant One',
|
|
||||||
'metadata' => [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Zero state
|
// Zero state
|
||||||
$url = InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant);
|
$url = InventoryItemResource::getUrl('view', ['record' => $item], panel: 'admin').'?tenant='.(string) $tenant->external_id;
|
||||||
$this->get($url)->assertOk()->assertSee('No dependencies found');
|
$this->get($url)->assertOk()->assertSee('No dependencies found');
|
||||||
|
|
||||||
// Create a missing edge and assert badge appears
|
// Create a missing edge and assert badge appears
|
||||||
@ -71,10 +71,10 @@
|
|||||||
'relationship_type' => 'depends_on',
|
'relationship_type' => 'depends_on',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$urlOutbound = InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant).'?direction=outbound';
|
$urlOutbound = InventoryItemResource::getUrl('view', ['record' => $item], panel: 'admin').'?tenant='.(string) $tenant->external_id.'&direction=outbound';
|
||||||
$this->get($urlOutbound)->assertOk()->assertDontSee('No dependencies found');
|
$this->get($urlOutbound)->assertOk()->assertDontSee('No dependencies found');
|
||||||
|
|
||||||
$urlInbound = InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant).'?direction=inbound';
|
$urlInbound = InventoryItemResource::getUrl('view', ['record' => $item], panel: 'admin').'?tenant='.(string) $tenant->external_id.'&direction=inbound';
|
||||||
$this->get($urlInbound)->assertOk()->assertDontSee('No dependencies found');
|
$this->get($urlInbound)->assertOk()->assertDontSee('No dependencies found');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -109,8 +109,8 @@
|
|||||||
'metadata' => ['last_known_name' => 'Scoped Target'],
|
'metadata' => ['last_known_name' => 'Scoped Target'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$url = InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant)
|
$url = InventoryItemResource::getUrl('view', ['record' => $item], panel: 'admin')
|
||||||
.'?direction=outbound&relationship_type=scoped_by';
|
.'?tenant='.(string) $tenant->external_id.'&direction=outbound&relationship_type=scoped_by';
|
||||||
|
|
||||||
$this->get($url)
|
$this->get($url)
|
||||||
->assertOk()
|
->assertOk()
|
||||||
@ -141,7 +141,7 @@
|
|||||||
'metadata' => ['last_known_name' => 'Other Tenant Edge'],
|
'metadata' => ['last_known_name' => 'Other Tenant Edge'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$url = InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant);
|
$url = InventoryItemResource::getUrl('view', ['record' => $item], panel: 'admin').'?tenant='.(string) $tenant->external_id;
|
||||||
$this->get($url)
|
$this->get($url)
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertDontSee('Other Tenant Edge');
|
->assertDontSee('Other Tenant Edge');
|
||||||
@ -170,7 +170,7 @@
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$url = InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant);
|
$url = InventoryItemResource::getUrl('view', ['record' => $item], panel: 'admin').'?tenant='.(string) $tenant->external_id;
|
||||||
$this->get($url)
|
$this->get($url)
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('Group (external): 123456…');
|
->assertSee('Group (external): 123456…');
|
||||||
@ -239,7 +239,7 @@
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$url = InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant);
|
$url = InventoryItemResource::getUrl('view', ['record' => $item], panel: 'admin').'?tenant='.(string) $tenant->external_id;
|
||||||
$this->get($url)
|
$this->get($url)
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('Scope Tag: Finance (6…)')
|
->assertSee('Scope Tag: Finance (6…)')
|
||||||
@ -286,7 +286,7 @@
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$url = InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant);
|
$url = InventoryItemResource::getUrl('view', ['record' => $item], panel: 'admin').'?tenant='.(string) $tenant->external_id;
|
||||||
$this->get($url)
|
$this->get($url)
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('Scope Tag: Finance');
|
->assertSee('Scope Tag: Finance');
|
||||||
@ -301,6 +301,6 @@
|
|||||||
'external_id' => (string) Str::uuid(),
|
'external_id' => (string) Str::uuid(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$url = InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant);
|
$url = InventoryItemResource::getUrl('view', ['record' => $item], panel: 'admin').'?tenant='.(string) $tenant->external_id;
|
||||||
$this->get($url)->assertRedirect();
|
$this->get($url)->assertRedirect();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -55,6 +55,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'is_current' => true,
|
'is_current' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
Policy::create([
|
Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'config-1',
|
'external_id' => 'config-1',
|
||||||
|
|||||||
@ -55,6 +55,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'is_current' => true,
|
'is_current' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
// Create an ignored policy
|
// Create an ignored policy
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
@ -100,6 +102,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'is_current' => true,
|
'is_current' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
// Create multiple ignored policies
|
// Create multiple ignored policies
|
||||||
Policy::create([
|
Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Filament\Resources\TenantResource;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Workspace;
|
use App\Models\Workspace;
|
||||||
@ -12,7 +13,7 @@
|
|||||||
[$user] = createUserWithTenant($tenant, role: 'readonly');
|
[$user] = createUserWithTenant($tenant, role: 'readonly');
|
||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->get("/admin/t/{$tenant->external_id}/tenants/{$tenant->id}/edit")
|
->get(TenantResource::getUrl('edit', ['record' => $tenant]))
|
||||||
->assertForbidden();
|
->assertForbidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,6 @@
|
|||||||
->get('/admin/operations')
|
->get('/admin/operations')
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee($workspaceName ?? 'Select workspace')
|
->assertSee($workspaceName ?? 'Select workspace')
|
||||||
->assertSee('Select tenant')
|
|
||||||
->assertSee('Search tenants…')
|
->assertSee('Search tenants…')
|
||||||
->assertSee('Switch workspace')
|
->assertSee('Switch workspace')
|
||||||
->assertSee('admin/select-tenant')
|
->assertSee('admin/select-tenant')
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
expect($failureSummaryJson)->not->toContain($rawBearer);
|
expect($failureSummaryJson)->not->toContain($rawBearer);
|
||||||
expect($failureSummaryJson)->not->toContain('test.user@example.com');
|
expect($failureSummaryJson)->not->toContain('test.user@example.com');
|
||||||
|
|
||||||
expect($run->failure_summary[0]['reason_code'] ?? null)->toBe('permission_denied');
|
expect($run->failure_summary[0]['reason_code'] ?? null)->toBe('provider_permission_denied');
|
||||||
|
|
||||||
$notification = DatabaseNotification::query()
|
$notification = DatabaseNotification::query()
|
||||||
->where('notifiable_id', $user->getKey())
|
->where('notifiable_id', $user->getKey())
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$tenant->makeCurrent();
|
$tenant->makeCurrent();
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
// Simulate an older bug: ESP row was synced under enrollmentRestriction.
|
// Simulate an older bug: ESP row was synced under enrollmentRestriction.
|
||||||
$wrong = Policy::create([
|
$wrong = Policy::create([
|
||||||
@ -81,6 +82,7 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$tenant->makeCurrent();
|
$tenant->makeCurrent();
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$this->mock(GraphClientInterface::class, function (MockInterface $mock) {
|
$this->mock(GraphClientInterface::class, function (MockInterface $mock) {
|
||||||
$payload = [
|
$payload = [
|
||||||
@ -172,6 +174,7 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$tenant->makeCurrent();
|
$tenant->makeCurrent();
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$this->mock(GraphClientInterface::class, function (MockInterface $mock) {
|
$this->mock(GraphClientInterface::class, function (MockInterface $mock) {
|
||||||
$limitPayload = [
|
$limitPayload = [
|
||||||
|
|||||||
@ -212,8 +212,8 @@
|
|||||||
|
|
||||||
$response = $this->get(route('filament.admin.resources.policy-versions.view', array_merge(
|
$response = $this->get(route('filament.admin.resources.policy-versions.view', array_merge(
|
||||||
filamentTenantRouteParams($this->tenant),
|
filamentTenantRouteParams($this->tenant),
|
||||||
['record' => $version],
|
['record' => $version, 'tab' => 'normalized-settings'],
|
||||||
)).'?tab=normalized-settings');
|
)));
|
||||||
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$response->assertSee('Password & Access');
|
$response->assertSee('Password & Access');
|
||||||
|
|||||||
@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
use App\Models\BackupItem;
|
use App\Models\BackupItem;
|
||||||
use App\Models\BackupSet;
|
use App\Models\BackupSet;
|
||||||
|
use App\Models\OperationRun;
|
||||||
use App\Models\Policy;
|
use App\Models\Policy;
|
||||||
use App\Models\ProviderConnection;
|
use App\Models\ProviderConnection;
|
||||||
use App\Models\ProviderCredential;
|
use App\Models\ProviderCredential;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
|
use App\Models\Workspace;
|
||||||
use App\Services\Graph\GraphClientInterface;
|
use App\Services\Graph\GraphClientInterface;
|
||||||
use App\Services\Graph\GraphResponse;
|
use App\Services\Graph\GraphResponse;
|
||||||
use App\Services\Graph\ScopeTagResolver;
|
use App\Services\Graph\ScopeTagResolver;
|
||||||
@ -16,6 +18,7 @@
|
|||||||
use App\Services\Intune\RbacHealthService;
|
use App\Services\Intune\RbacHealthService;
|
||||||
use App\Services\Intune\RestoreService;
|
use App\Services\Intune\RestoreService;
|
||||||
use App\Services\Inventory\InventorySyncService;
|
use App\Services\Inventory\InventorySyncService;
|
||||||
|
use App\Support\OperationRunType;
|
||||||
use App\Support\RbacReason;
|
use App\Support\RbacReason;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
@ -26,11 +29,14 @@
|
|||||||
*/
|
*/
|
||||||
function spec081TenantWithDefaultMicrosoftConnection(string $tenantId): array
|
function spec081TenantWithDefaultMicrosoftConnection(string $tenantId): array
|
||||||
{
|
{
|
||||||
|
$workspace = Workspace::factory()->create();
|
||||||
|
|
||||||
$tenant = Tenant::factory()->create([
|
$tenant = Tenant::factory()->create([
|
||||||
'tenant_id' => $tenantId,
|
'tenant_id' => $tenantId,
|
||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'app_client_id' => null,
|
'app_client_id' => null,
|
||||||
'app_client_secret' => null,
|
'app_client_secret' => null,
|
||||||
|
'workspace_id' => (int) $workspace->getKey(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$connection = ProviderConnection::factory()->create([
|
$connection = ProviderConnection::factory()->create([
|
||||||
@ -79,17 +85,23 @@ function spec081TenantWithDefaultMicrosoftConnection(string $tenantId): array
|
|||||||
|
|
||||||
app()->instance(GraphClientInterface::class, $graph);
|
app()->instance(GraphClientInterface::class, $graph);
|
||||||
|
|
||||||
$run = app(InventorySyncService::class)->syncNow(
|
$service = app(InventorySyncService::class);
|
||||||
$setup['tenant'],
|
$selection = [
|
||||||
[
|
|
||||||
'policy_types' => ['deviceConfiguration'],
|
'policy_types' => ['deviceConfiguration'],
|
||||||
'categories' => ['Configuration'],
|
'categories' => ['Configuration'],
|
||||||
'include_foundations' => false,
|
'include_foundations' => false,
|
||||||
'include_dependencies' => false,
|
'include_dependencies' => false,
|
||||||
],
|
];
|
||||||
);
|
|
||||||
|
|
||||||
expect($run->status)->toBe('success');
|
$opRun = OperationRun::factory()->create([
|
||||||
|
'tenant_id' => (int) $setup['tenant']->getKey(),
|
||||||
|
'workspace_id' => (int) $setup['tenant']->workspace_id,
|
||||||
|
'type' => OperationRunType::InventorySync->value,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = $service->executeSelection($opRun, $setup['tenant'], $selection);
|
||||||
|
|
||||||
|
expect($result['status'])->toBe('success');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Spec081 smoke: policy sync uses provider connection credentials with tenant secrets empty', function (): void {
|
it('Spec081 smoke: policy sync uses provider connection credentials with tenant secrets empty', function (): void {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Filament\Pages\Workspaces\ManagedTenantOnboardingWizard;
|
use App\Filament\Pages\Workspaces\ManagedTenantOnboardingWizard;
|
||||||
use App\Jobs\ProviderConnectionHealthCheckJob;
|
use App\Models\OperationRun;
|
||||||
use App\Models\ProviderConnection;
|
use App\Models\ProviderConnection;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Models\TenantOnboardingSession;
|
use App\Models\TenantOnboardingSession;
|
||||||
@ -101,6 +101,14 @@
|
|||||||
->test(ManagedTenantOnboardingWizard::class)
|
->test(ManagedTenantOnboardingWizard::class)
|
||||||
->call('startVerification');
|
->call('startVerification');
|
||||||
|
|
||||||
Queue::assertPushed(ProviderConnectionHealthCheckJob::class, 1);
|
$run = OperationRun::query()
|
||||||
|
->where('tenant_id', (int) $tenant->getKey())
|
||||||
|
->where('type', 'provider.connection.check')
|
||||||
|
->latest('id')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
expect($run)->not->toBeNull();
|
||||||
|
|
||||||
|
Queue::assertNothingPushed();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Filament\Pages\Tenancy\RegisterTenant;
|
use App\Filament\Pages\Tenancy\RegisterTenant;
|
||||||
|
use Filament\Facades\Filament;
|
||||||
|
|
||||||
describe('Register tenant page authorization', function () {
|
describe('Register tenant page authorization', function () {
|
||||||
it('is not visible for readonly members', function () {
|
it('is not visible for readonly members', function () {
|
||||||
@ -9,7 +10,11 @@
|
|||||||
$this->actingAs($user);
|
$this->actingAs($user);
|
||||||
$tenant->makeCurrent();
|
$tenant->makeCurrent();
|
||||||
|
|
||||||
|
Filament::setCurrentPanel(Filament::getPanel('tenant'));
|
||||||
|
|
||||||
expect(RegisterTenant::canView())->toBeFalse();
|
expect(RegisterTenant::canView())->toBeFalse();
|
||||||
|
|
||||||
|
Filament::setCurrentPanel(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is visible for owner members', function () {
|
it('is visible for owner members', function () {
|
||||||
@ -18,6 +23,10 @@
|
|||||||
$this->actingAs($user);
|
$this->actingAs($user);
|
||||||
$tenant->makeCurrent();
|
$tenant->makeCurrent();
|
||||||
|
|
||||||
|
Filament::setCurrentPanel(Filament::getPanel('tenant'));
|
||||||
|
|
||||||
expect(RegisterTenant::canView())->toBeTrue();
|
expect(RegisterTenant::canView())->toBeTrue();
|
||||||
|
|
||||||
|
Filament::setCurrentPanel(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use App\Filament\Pages\Tenancy\RegisterTenant;
|
use App\Filament\Pages\Tenancy\RegisterTenant;
|
||||||
use App\Filament\Resources\TenantResource\Pages\CreateTenant;
|
use App\Filament\Resources\TenantResource\Pages\CreateTenant;
|
||||||
|
use Filament\Facades\Filament;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
|
|
||||||
@ -12,11 +13,15 @@
|
|||||||
|
|
||||||
$this->actingAs($user);
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
Filament::setCurrentPanel(Filament::getPanel('tenant'));
|
||||||
|
|
||||||
expect(RegisterTenant::canView())->toBeFalse();
|
expect(RegisterTenant::canView())->toBeFalse();
|
||||||
|
|
||||||
Livewire::actingAs($user)
|
Livewire::actingAs($user)
|
||||||
->test(RegisterTenant::class)
|
->test(RegisterTenant::class)
|
||||||
->assertStatus(404);
|
->assertStatus(404);
|
||||||
|
|
||||||
|
Filament::setCurrentPanel(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('readonly users cannot create tenants', function () {
|
test('readonly users cannot create tenants', function () {
|
||||||
|
|||||||
@ -81,6 +81,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'scp-1',
|
'external_id' => 'scp-1',
|
||||||
@ -185,6 +187,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'scp-1',
|
'external_id' => 'scp-1',
|
||||||
@ -271,6 +275,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
'metadata' => [],
|
'metadata' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$policy = Policy::create([
|
$policy = Policy::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'scp-1',
|
'external_id' => 'scp-1',
|
||||||
|
|||||||
@ -65,6 +65,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
$backupSet = BackupSet::factory()->for($tenant)->create([
|
$backupSet = BackupSet::factory()->for($tenant)->create([
|
||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
'item_count' => 1,
|
'item_count' => 1,
|
||||||
|
|||||||
@ -63,6 +63,8 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
|
|
||||||
$backupSet = BackupSet::factory()->for($tenant)->create([
|
$backupSet = BackupSet::factory()->for($tenant)->create([
|
||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
'item_count' => 2,
|
'item_count' => 2,
|
||||||
|
|||||||
@ -82,6 +82,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
config()->set('graph_contracts.types.securityBaselinePolicy', []);
|
config()->set('graph_contracts.types.securityBaselinePolicy', []);
|
||||||
|
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
$backupSet = BackupSet::factory()->for($tenant)->create([
|
$backupSet = BackupSet::factory()->for($tenant)->create([
|
||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
'item_count' => 1,
|
'item_count' => 1,
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Models\TenantMembership;
|
use App\Models\TenantMembership;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Filament\Facades\Filament;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
|
|
||||||
@ -19,6 +20,8 @@
|
|||||||
|
|
||||||
$this->actingAs($user);
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
Filament::setCurrentPanel(Filament::getPanel('tenant'));
|
||||||
|
|
||||||
$tenantGuid = '11111111-1111-1111-1111-111111111111';
|
$tenantGuid = '11111111-1111-1111-1111-111111111111';
|
||||||
|
|
||||||
Livewire::test(RegisterTenant::class)
|
Livewire::test(RegisterTenant::class)
|
||||||
@ -28,6 +31,8 @@
|
|||||||
->set('data.domain', 'acme.example')
|
->set('data.domain', 'acme.example')
|
||||||
->call('register');
|
->call('register');
|
||||||
|
|
||||||
|
Filament::setCurrentPanel(null);
|
||||||
|
|
||||||
$tenant = Tenant::query()->where('tenant_id', $tenantGuid)->firstOrFail();
|
$tenant = Tenant::query()->where('tenant_id', $tenantGuid)->firstOrFail();
|
||||||
|
|
||||||
$membership = TenantMembership::query()
|
$membership = TenantMembership::query()
|
||||||
|
|||||||
@ -95,6 +95,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
app()->instance(GraphClientInterface::class, $client);
|
app()->instance(GraphClientInterface::class, $client);
|
||||||
|
|
||||||
$tenant = Tenant::factory()->create(['tenant_id' => 'tenant-1']);
|
$tenant = Tenant::factory()->create(['tenant_id' => 'tenant-1']);
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
$policy = Policy::factory()->create([
|
$policy = Policy::factory()->create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'external_id' => 'tc-1',
|
'external_id' => 'tc-1',
|
||||||
@ -182,6 +183,7 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
|
|
||||||
it('syncs terms and conditions from graph', function () {
|
it('syncs terms and conditions from graph', function () {
|
||||||
$tenant = Tenant::factory()->create(['status' => 'active']);
|
$tenant = Tenant::factory()->create(['status' => 'active']);
|
||||||
|
ensureDefaultProviderConnection($tenant);
|
||||||
$logger = mock(GraphLogger::class);
|
$logger = mock(GraphLogger::class);
|
||||||
|
|
||||||
$logger->shouldReceive('logRequest')
|
$logger->shouldReceive('logRequest')
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Models\ProviderConnection;
|
||||||
|
use App\Models\ProviderCredential;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Workspace;
|
use App\Models\Workspace;
|
||||||
@ -179,3 +181,41 @@ function filamentTenantRouteParams(Tenant $tenant): array
|
|||||||
{
|
{
|
||||||
return ['tenant' => (string) $tenant->external_id];
|
return ['tenant' => (string) $tenant->external_id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureDefaultProviderConnection(Tenant $tenant, string $provider = 'microsoft'): ProviderConnection
|
||||||
|
{
|
||||||
|
$connection = ProviderConnection::query()
|
||||||
|
->where('tenant_id', (int) $tenant->getKey())
|
||||||
|
->where('provider', $provider)
|
||||||
|
->where('is_default', true)
|
||||||
|
->orderBy('id')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $connection instanceof ProviderConnection) {
|
||||||
|
$connection = ProviderConnection::factory()->create([
|
||||||
|
'tenant_id' => (int) $tenant->getKey(),
|
||||||
|
'provider' => $provider,
|
||||||
|
'entra_tenant_id' => (string) ($tenant->tenant_id ?? fake()->uuid()),
|
||||||
|
'status' => 'connected',
|
||||||
|
'health_status' => 'ok',
|
||||||
|
'is_default' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$credential = $connection->credential()->first();
|
||||||
|
|
||||||
|
if (! $credential instanceof ProviderCredential) {
|
||||||
|
ProviderCredential::factory()->create([
|
||||||
|
'provider_connection_id' => (int) $connection->getKey(),
|
||||||
|
'type' => 'client_secret',
|
||||||
|
'payload' => [
|
||||||
|
'client_id' => fake()->uuid(),
|
||||||
|
'client_secret' => fake()->sha1(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$connection->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $connection;
|
||||||
|
}
|
||||||
|
|||||||
@ -17,16 +17,16 @@
|
|||||||
|
|
||||||
$patterns = [
|
$patterns = [
|
||||||
// $membership->role === 'owner' / !== 'owner'
|
// $membership->role === 'owner' / !== 'owner'
|
||||||
'/->role\s*(===|==|!==|!=)\s*[\"\']?'.$roleValuePattern.'[\"\']?/i',
|
'/->role\s*(===|==|!==|!=)\s*(["\'])'.$roleValuePattern.'\2/i',
|
||||||
|
|
||||||
// $role === 'owner'
|
// $role === 'owner'
|
||||||
'/\$role\s*(===|==|!==|!=)\s*[\"\']?'.$roleValuePattern.'[\"\']?/i',
|
'/\$role\s*(===|==|!==|!=)\s*(["\'])'.$roleValuePattern.'\2/i',
|
||||||
|
|
||||||
// case 'owner':
|
// case 'owner':
|
||||||
'/\bcase\s*[\"\']?'.$roleValuePattern.'[\"\']?\s*:/i',
|
'/\bcase\s*(["\'])'.$roleValuePattern.'\1\s*:/i',
|
||||||
|
|
||||||
// match (...) { 'owner' => ... }
|
// match (...) { 'owner' => ... }
|
||||||
'/\bmatch\b[\s\S]*?\{[\s\S]*?[\"\']?'.$roleValuePattern.'[\"\']?\s*=>/i',
|
'/\bmatch\b[\s\S]*?\{[\s\S]*?(["\'])'.$roleValuePattern.'\1\s*=>/i',
|
||||||
];
|
];
|
||||||
|
|
||||||
$filesystem = new Filesystem;
|
$filesystem = new Filesystem;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user