diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 93c9a35..e094d2b 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -53,7 +53,8 @@ class UserController extends AbstractController private readonly LoggerInterface $userManagementLogger, private readonly LoggerInterface $organizationManagementLogger, private readonly LoggerInterface $errorLogger, - private readonly LoggerService $loggerService, + private readonly LoggerInterface $securityLogger, + private readonly LoggerService $loggerService, private readonly EmailService $emailService, private readonly AwsService $awsService, private readonly OrganizationsService $organizationsService, @@ -169,29 +170,22 @@ class UserController extends AbstractController public function edit(int $id, Request $request): Response { $this->denyAccessUnlessGranted('ROLE_USER'); - try{ + try { $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); if ($this->userService->hasAccessTo($actingUser)) { $user = $this->userRepository->find($id); if (!$user) { $this->userManagementLogger->notice('User not found for edit', [ - 'target_user_id' => $user->getId(), - 'acting_user_id' => $actingUser->getId(), - 'ip' => $request->getClientIp(), - 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), + 'target_user_id' => $user->getId(), + 'acting_user_id' => $actingUser->getId(), + 'ip' => $request->getClientIp(), + 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), ]); throw $this->createNotFoundException(self::NOT_FOUND); } $form = $this->createForm(UserForm::class, $user); $form->handleRequest($request); - $this->userManagementLogger->notice('Format test', [ - 'target_user_id' => $user->getId(), - 'acting_user_id' => $actingUser->getId(), - 'ip' => $request->getClientIp(), - 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), - ]); - if ($form->isSubmitted() && $form->isValid()) { // Handle user edit @@ -204,21 +198,21 @@ class UserController extends AbstractController //log and action $this->userManagementLogger->notice('User information edited', [ - 'target_user_id' => $user->getId(), - 'acting_user_id' => $actingUser->getId(), - 'organization_id' => $request->get('organizationId'), - 'ip' => $request->getClientIp(), - 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), + 'target_user_id' => $user->getId(), + 'acting_user_id' => $actingUser->getId(), + 'organization_id' => $request->get('organizationId'), + 'ip' => $request->getClientIp(), + 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), ]); if ($request->get('organizationId')) { $org = $this->organizationRepository->find($request->get('organizationId')); if ($org) { $this->actionService->createAction("Edit user information", $actingUser, $org, $user->getUserIdentifier()); $this->organizationManagementLogger->info('User edited within organization context', [ - 'target_user_id' => $user->getId(), + 'target_user_id' => $user->getId(), 'organization_id' => $org->getId(), - 'acting_user' => $actingUser->getUserIdentifier(), - 'ip' => $request->getClientIp(), + 'acting_user' => $actingUser->getUserIdentifier(), + 'ip' => $request->getClientIp(), ]); return $this->redirectToRoute('user_show', ['id' => $user->getId(), 'organizationId' => $request->get('organizationId')]); } @@ -234,13 +228,13 @@ class UserController extends AbstractController 'organizationId' => $request->get('organizationId') ]); } - }catch (\Exception $e){ + } catch (\Exception $e) { $this->errorLogger->critical($e->getMessage()); } - $this->SecurityLogger->warning('Access denied on user edit', [ + $this->securityLogger->warning('Access denied on user edit', [ 'target_user_id' => $id, - 'acting_user' => $actingUser?->getId(), - 'ip' => $request->getClientIp(), + 'acting_user' => $actingUser?->getId(), + 'ip' => $request->getClientIp(), ]); throw $this->createAccessDeniedException(self::ACCESS_DENIED); } @@ -267,8 +261,8 @@ class UserController extends AbstractController if (!$org && $orgId) { $this->loggerService->logCritical('Organization not found for user creation', [ 'organization_id' => $orgId, - 'acting_user_id' => $actingUser->getId(), - 'ip' => $request->getClientIp(), + 'acting_user_id' => $actingUser->getId(), + 'ip' => $request->getClientIp(), ]); throw $this->createNotFoundException('Organization not found'); } @@ -302,8 +296,8 @@ class UserController extends AbstractController if ($existingUser) { $this->loggerService->logError('Attempt to create user with existing email without organization', [ 'target_user_email' => $user->getid(), - 'acting_user_id' => $actingUser->getId(), - 'ip' => $request->getClientIp(), + 'acting_user_id' => $actingUser->getId(), + 'ip' => $request->getClientIp(), ]); $form->get('email')->addError( @@ -313,8 +307,8 @@ class UserController extends AbstractController ); return $this->render('user/new.html.twig', [ - 'user' => $user, - 'form' => $form->createView(), + 'user' => $user, + 'form' => $form->createView(), 'organizationId' => $orgId, ]); } @@ -359,8 +353,8 @@ class UserController extends AbstractController } return $this->render('user/new.html.twig', [ - 'user' => $user, - 'form' => $form->createView(), + 'user' => $user, + 'form' => $form->createView(), 'organizationId' => $orgId, ]); @@ -382,90 +376,136 @@ class UserController extends AbstractController public function activeStatus(int $id, Request $request): JsonResponse { $this->denyAccessUnlessGranted('ROLE_ADMIN'); - $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); - try{ - if ($this->userService->hasAccessTo($actingUser, true)) { - $user = $this->userRepository->find($id); - if (!$user) { - throw $this->createNotFoundException(self::NOT_FOUND); - } - $status = $request->get('status'); - if ($status === 'deactivate') { - $user->setIsActive(false); - $this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, $user); - if ($this->userService->isUserConnected($user->getUserIdentifier())) { - $this->userService->revokeUserTokens($user->getUserIdentifier()); - } - $user->setModifiedAt(new \DateTimeImmutable('now')); - $this->entityManager->persist($user); - $this->entityManager->flush(); - $this->logger->notice("User deactivated " . $user->getUserIdentifier()); - $this->actionService->createAction("Deactivate user", $actingUser, null, $user->getUserIdentifier()); - return new JsonResponse(['status' => 'deactivated'], Response::HTTP_OK); - } - if ($status === 'activate') { - $user->setIsActive(true); - $user->setModifiedAt(new \DateTimeImmutable('now')); - $this->logger->notice("User activated " . $user->getUserIdentifier()); - $this->actionService->createAction("Activate user", $actingUser, null, $user->getUserIdentifier()); - return new JsonResponse(['status' => 'activated'], Response::HTTP_OK); - } - } - }catch (\Exception $e){ - $this->logger->error($e->getMessage()); - } - throw $this->createNotFoundException(self::NOT_FOUND); - } - - #[Route('/organization/activateStatus/{id}', name: 'activate_organization', methods: ['GET', 'POST'])] - public function activateStatusOrganization(int $id, Request $request): JsonResponse{ - $this->denyAccessUnlessGranted('ROLE_ADMIN'); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); + $ip = $request->getClientIp(); + $status = $request->get('status'); + try { - if ($this->userService->hasAccessTo($actingUser, true)) { - $orgId = $request->get('organizationId'); - $org = $this->organizationRepository->find($orgId); - if (!$org) { - throw $this->createNotFoundException(self::NOT_FOUND); - } - $user = $this->userRepository->find($id); - if (!$user) { - throw $this->createNotFoundException(self::NOT_FOUND); - } - $uo = $this->uoRepository->findOneBy(['users' => $user, - 'organization' => $org]); - if (!$uo) { - throw $this->createNotFoundException(self::NOT_FOUND); - } - $status = $request->get('status'); - if ($status === 'deactivate') { - $uo->setIsActive(false); - $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo); - $this->entityManager->persist($uo); - $this->entityManager->flush(); - $data = ['user' => $user, - 'organization' => $org]; - $this->organizationsService->notifyOrganizationAdmins($data, "USER_DEACTIVATED"); - $this->logger->notice("User Organizaton deactivated " . $user->getUserIdentifier()); - $this->actionService->createAction("Deactivate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier()); - return new JsonResponse(['status' => 'deactivated'], Response::HTTP_OK); - } - if($status === "activate"){ - $uo->setIsActive(true); - $this->entityManager->persist($uo); - $this->entityManager->flush(); - $this->actionService->createAction("Activate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier()); - $data = ['user' => $user, - 'organization' => $org]; - $this->organizationsService->notifyOrganizationAdmins($data, "USER_ACTIVATED"); - return new JsonResponse(['status' => 'activated'], Response::HTTP_OK); - } + // Access control + if (!$this->userService->hasAccessTo($actingUser, true)) { + $this->securityLogger->warning('Access denied on user status change', [ + 'target_user_id' => $id, + 'acting_user_id' => $actingUser?->getId(), + 'acting_identifier' => $actingUser?->getUserIdentifier(), + 'requested_status' => $status, + 'ip' => $ip, + ]); + + throw $this->createAccessDeniedException(self::ACCESS_DENIED); } - }catch (\Exception $exception){ - $this->logger->error($exception->getMessage()); + + // Load target user + $user = $this->userRepository->find($id); + if (!$user) { + $this->securityLogger->warning('User not found for status change', [ + 'target_user_id' => $id, + 'acting_user_id' => $actingUser->getId(), + 'requested_status' => $status, + 'ip' => $ip, + ]); + + throw $this->createNotFoundException(self::NOT_FOUND); + } + + // Deactivate + if ($status === 'deactivate') { + $user->setIsActive(false); + + $this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, $user); + + if ($this->userService->isUserConnected($user->getUserIdentifier())) { + $this->userService->revokeUserTokens($user->getUserIdentifier()); + + $this->securityLogger->info('User tokens revoked due to deactivation', [ + 'target_user_id' => $user->getId(), + 'target_identifier' => $user->getUserIdentifier(), + 'acting_user_id' => $actingUser->getId(), + 'ip' => $ip, + ]); + } + + $user->setModifiedAt(new \DateTimeImmutable('now')); + $this->entityManager->flush(); + + $this->userManagementLogger->notice('User deactivated', [ + 'target_user_id' => $user->getId(), + 'target_identifier' => $user->getUserIdentifier(), + 'acting_user_id' => $actingUser->getId(), + 'ip' => $ip, + ]); + + if ($this->isGranted('ROLE_SUPER_ADMIN')) { + $this->loggerService->logSuperAdmin( + $user->getId(), + null, + $actingUser->getId(), + $ip, + 'Super admin deactivated user' + ); + } + + $this->actionService->createAction('Deactivate user', $actingUser, null, $user->getUserIdentifier()); + + return new JsonResponse(['status' => 'deactivated'], Response::HTTP_OK); + } + + // Activate + if ($status === 'activate') { + $user->setIsActive(true); + $user->setModifiedAt(new \DateTimeImmutable('now')); + $this->entityManager->flush(); + + $this->userManagementLogger->notice('User activated', [ + 'target_user_id' => $user->getId(), + 'target_identifier' => $user->getUserIdentifier(), + 'acting_user_id' => $actingUser->getId(), + 'ip' => $ip, + ]); + + if ($this->isGranted('ROLE_SUPER_ADMIN')) { + $this->loggerService->logSuperAdmin( + $user->getId(), + null, + $actingUser->getId(), + $ip, + 'Super admin activated user' + ); + } + + $this->actionService->createAction('Activate user', $actingUser, null, $user->getUserIdentifier()); + + return new JsonResponse(['status' => 'activated'], Response::HTTP_OK); + } + + // Invalid status + $this->loggerService->warning('Invalid status passed to activeStatus', [ + 'target_user_id' => $user->getId(), + 'acting_user_id' => $actingUser->getId(), + 'requested_status' => $status, + 'ip' => $ip, + ]); + + return new JsonResponse(['error' => 'Invalid status'], Response::HTTP_BAD_REQUEST); + + } catch (\Throwable $e) { + // Application-level error logging → error.log (via error channel) + $this->errorLogger->error('Error in activeStatus', [ + 'exception_message' => $e->getMessage(), + 'exception_class' => get_class($e), + 'target_user_id' => $id, + 'acting_user_id' => $actingUser?->getId(), + 'requested_status' => $status, + 'ip' => $ip, + ]); + + // Preserve 403/404 semantics, 500 for everything else + if ($e instanceof NotFoundHttpException || $e instanceof AccessDeniedException) { + throw $e; + } + + return new JsonResponse(['error' => 'An error occurred'], Response::HTTP_INTERNAL_SERVER_ERROR); } - throw $this->createNotFoundException(self::NOT_FOUND); } //TODO : MONOLOG + remove picture from bucket diff --git a/src/Service/LoggerService.php b/src/Service/LoggerService.php index 016fa21..433ea71 100644 --- a/src/Service/LoggerService.php +++ b/src/Service/LoggerService.php @@ -97,7 +97,7 @@ readonly class LoggerService ]); } - public function logSuperAdmin(int $userId, ?int $orgId, int $actingUserId, ?string $ip, string $message): void + public function logSuperAdmin(int $userId, ?int $orgId = null, int $actingUserId, ?string $ip, string $message): void { $this->adminActionsLogger->notice($message, [ 'target_user_id' => $userId,