diff --git a/backend/src/Controller/ImpersonationController.php b/backend/src/Controller/ImpersonationController.php new file mode 100644 index 0000000..3f137ca --- /dev/null +++ b/backend/src/Controller/ImpersonationController.php @@ -0,0 +1,85 @@ +em->getRepository(Employee::class)->find(Uuid::fromString($id)); + if (!$target instanceof Employee) { + throw new NotFoundHttpException('Mitarbeiter nicht gefunden.'); + } + if (!$target->hasLogin()) { + throw new BadRequestHttpException('Dieser Mitarbeiter hat kein Login.'); + } + + $this->assertInScope($target); + + // Nur absteigend: Ziel-Ebene muss unter der eigenen liegen + if ($this->roles->levelOfRoles($target->getRoles()) >= $this->roles->actorLevel()) { + throw new AccessDeniedHttpException('Nur als niedrigere Ebene möglich.'); + } + + $actor = $this->security->getUser(); + $impersonator = $actor instanceof Employee ? $actor->getUserIdentifier() : ''; + + $token = $this->jwt->createFromPayload($target, ['imp' => $impersonator]); + + return new JsonResponse([ + 'token' => $token, + 'actingAs' => [ + 'name' => trim($target->getFirstName().' '.$target->getLastName()), + 'email' => $target->getLoginEmail(), + ], + ]); + } + + private function assertInScope(Employee $target): void + { + if ($this->tenant->isPlatformAdmin()) { + return; + } + if (null !== $company = $this->tenant->getCompany()) { + if (!$target->getCompany()->getId()->equals($company->getId())) { + throw new AccessDeniedHttpException('Außerhalb der eigenen Firma.'); + } + + return; + } + if (null !== $reseller = $this->tenant->getReseller()) { + if ($target->getCompany()->getReseller()?->getId()->equals($reseller->getId()) !== true) { + throw new AccessDeniedHttpException('Außerhalb des eigenen Resellers.'); + } + } + } +} diff --git a/backend/src/Service/RoleService.php b/backend/src/Service/RoleService.php index 42e7b40..9a9d1de 100644 --- a/backend/src/Service/RoleService.php +++ b/backend/src/Service/RoleService.php @@ -52,6 +52,19 @@ final class RoleService return 0; } + /** Höchste Ebene aus einer Rollenliste (z. B. eines Ziel-Mitarbeiters). */ + public function levelOfRoles(array $roles): int + { + $level = 0; + foreach (self::GROUPS as $g) { + if (in_array($g['role'], $roles, true)) { + $level = max($level, $g['level']); + } + } + + return $level; + } + /** Rechtegruppen, die der Akteur vergeben darf (≤ eigene Ebene). */ public function assignableGroups(): array { diff --git a/frontend/src/layouts/DashboardLayout.vue b/frontend/src/layouts/DashboardLayout.vue index 586bceb..dad0db0 100644 --- a/frontend/src/layouts/DashboardLayout.vue +++ b/frontend/src/layouts/DashboardLayout.vue @@ -29,6 +29,11 @@ function logout() { auth.logout() router.push('/login') } + +async function stopImpersonation() { + await auth.stopImpersonation() + router.push('/app') +}