diff --git a/app/Domains/Core/Auth/Services/AdminAppContextService.php b/app/Domains/Core/Auth/Services/AdminAppContextService.php index 1e2b5e66..1b9602bc 100644 --- a/app/Domains/Core/Auth/Services/AdminAppContextService.php +++ b/app/Domains/Core/Auth/Services/AdminAppContextService.php @@ -23,7 +23,9 @@ class AdminAppContextService $user->load('event'); if (! $user->isTenantAdministrator()) { - $tenant->setRelation('menues', $tenant->menues->where('code', 'main.adminapp')->values()); + $allowed = ['main.adminapp', 'adminapp.inicio', 'adminapp.event', + 'adminapp.catalog', 'adminapp.ventas', 'adminapp.staff']; + $tenant->setRelation('menues', $tenant->menues->whereIn('code', $allowed)->values()); } return $user; diff --git a/app/Domains/Core/Auth/Services/EventScopeService.php b/app/Domains/Core/Auth/Services/EventScopeService.php new file mode 100644 index 00000000..d31054dc --- /dev/null +++ b/app/Domains/Core/Auth/Services/EventScopeService.php @@ -0,0 +1,24 @@ +admin_scope === AdminScope::Event->value || $user->event_id !== null) { + if ($user->event_id === null + || ! $user->event()->where('tenant_code', $user->tenant_codigo)->exists()) { + throw new AuthorizationException; + } + + return $user->event_id; + } + + return null; + } +} diff --git a/app/Http/Middleware/EnsureAdminAppTenant.php b/app/Http/Middleware/EnsureAdminAppTenant.php index c43e9316..433be6b5 100644 --- a/app/Http/Middleware/EnsureAdminAppTenant.php +++ b/app/Http/Middleware/EnsureAdminAppTenant.php @@ -25,8 +25,8 @@ class EnsureAdminAppTenant || $user->rol_codigo !== RoleCode::AdminApp->value || ! $user->tenant_codigo || ! $this->accessService->hasValidScope($user) - // Tenant operations remain unavailable until they implement event authorization. - || ($access !== 'context' && ! $user->isTenantAdministrator()) + // Only routes implementing event authorization may accept event administrators. + || (! in_array($access, ['context', 'event'], true) && ! $user->isTenantAdministrator()) ) { throw new AuthorizationException; } diff --git a/database/migrations/2026_09_30_000100_assign_active_event_to_staff_users.php b/database/migrations/2026_09_30_000100_assign_active_event_to_staff_users.php new file mode 100644 index 00000000..20bdeea0 --- /dev/null +++ b/database/migrations/2026_09_30_000100_assign_active_event_to_staff_users.php @@ -0,0 +1,44 @@ +join('events', 'events.id', '=', 'tenants.active_event_id') + ->whereColumn('events.tenant_code', 'tenants.codigo') + ->select('tenants.codigo', 'tenants.active_event_id') + ->get() + ->each(function (object $tenant): void { + DB::table('users') + ->whereIn('rol_codigo', ['adminapp', 'scanner']) + ->where('tenant_codigo', $tenant->codigo) + ->where('admin_scope', 'tenant') + ->whereNull('event_id') + ->whereNull('deleted_at') + ->update([ + 'admin_scope' => 'event', + 'event_id' => $tenant->active_event_id, + 'updated_at' => now(), + ]); + }); + + // Missing or mismatched active events must not leave legacy staff with tenant-wide access. + DB::table('users') + ->whereIn('rol_codigo', ['adminapp', 'scanner']) + ->where('admin_scope', 'tenant') + ->whereNull('event_id') + ->whereNull('deleted_at') + ->update(['admin_scope' => 'event', 'updated_at' => now()]); + }); + } + + public function down(): void + { + // Do not broaden permissions or overwrite subsequent assignments on rollback. + } +}; diff --git a/tests/Feature/Auth/AdminAppEventScopeTest.php b/tests/Feature/Auth/AdminAppEventScopeTest.php index dfb97bbd..e3608a00 100644 --- a/tests/Feature/Auth/AdminAppEventScopeTest.php +++ b/tests/Feature/Auth/AdminAppEventScopeTest.php @@ -93,9 +93,9 @@ class AdminAppEventScopeTest extends TestCase $this->user = User::query()->findOrFail(1); DB::table('menues')->insert([ ['code' => 'main.adminapp', 'label' => 'Administración', 'route' => '/', 'parent_menu_code' => null], - ['code' => 'onticket.adminapp.ventas', 'label' => 'Ventas', 'route' => '/admin/ventas', 'parent_menu_code' => 'main.adminapp'], + ['code' => 'adminapp.ventas', 'label' => 'Ventas', 'route' => '/admin/ventas', 'parent_menu_code' => 'main.adminapp'], ]); - foreach (['main.adminapp', 'onticket.adminapp.ventas'] as $code) { + foreach (['main.adminapp', 'adminapp.ventas'] as $code) { DB::table('roles_menues')->insert(['rol_codigo' => 'adminapp', 'menu_codigo' => $code]); DB::table('tenants_menues')->insert(['tenant_code' => 'onticket', 'menu_code' => $code]); } @@ -132,7 +132,7 @@ class AdminAppEventScopeTest extends TestCase $this->withToken($token)->getJson('/api/v1/adminapp/me')->assertOk() ->assertJsonPath('data.event.id', 1)->assertJsonPath('data.event.title', 'Evento A') ->assertJsonPath('data.tenant.codigo', 'onticket') - ->assertJsonCount(0, 'data.tenant.menues.0.submenues'); + ->assertJsonCount(1, 'data.tenant.menues.0.submenues'); $this->assertSame(['adminapp'], $this->user->tokens()->sole()->abilities); } @@ -156,7 +156,7 @@ class AdminAppEventScopeTest extends TestCase { $this->user->update(['admin_scope' => 'event', 'event_id' => 1]); $token = $this->login()->assertOk()->json('token'); - foreach (['tenant/sales', 'tenant/tickets', 'tenant/event', 'tenant/administrators', 'forms/event'] as $path) { + foreach (['tenant/tickets', 'tenant/administrators', 'tenant/website-extras', 'forms/tickets-filter'] as $path) { $this->withToken($token)->getJson('/api/v1/adminapp/'.$path)->assertForbidden(); } $this->withToken($token)->postJson('/api/v1/adminapp/tenant/administrators', [])->assertForbidden();