diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 9ea51a6..9b2a988 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -5,6 +5,7 @@ namespace App\Controller; use App\Repository\UserRepository; use App\Repository\UsersOrganizationsRepository; use App\Service\AccessTokenService; +use App\Service\LoggerService; use App\Service\OrganizationsService; use App\Service\UserService; use Doctrine\ORM\EntityManagerInterface; @@ -30,7 +31,7 @@ class SecurityController extends AbstractController private readonly UsersOrganizationsRepository $uoRepository, private readonly LoggerInterface $logger, private readonly EntityManagerInterface $entityManager, - private readonly OrganizationsService $organizationsService) + private readonly OrganizationsService $organizationsService, private readonly LoggerService $loggerService, private readonly Security $security) { $this->cguUserService = $cguUserService; } @@ -50,14 +51,16 @@ class SecurityController extends AbstractController #[Route(path: '/sso_logout', name: 'sso_logout')] public function ssoLogout(RequestStack $stack, LoggerInterface $logger, AccessTokenService $accessTokenService, Security $security): Response { - try{ - if( $stack->getSession()->invalidate()){ - $accessTokenService->revokeTokens($security->getUser()->getUserIdentifier()); + try { + $user = $this->userService->getUserByIdentifier($this->security->getUser()->getUserIdentifier()); + $id = $user->getId(); + if ($stack->getSession()->invalidate()) { + $accessTokenService->revokeUserTokens($security->getUser()->getUserIdentifier()); $security->logout(false); - $logger->info("Logout successfully"); - return $this->redirect('/'); + $this->loggerService->logUserConnection('User logged out', ['user_id' => $id]); + return $this->redirect('/'); } - }catch (\Exception $e){ + } catch (\Exception $e) { $logger->log(LogLevel::ERROR, 'Error invalidating session: ' . $e->getMessage()); } return $this->redirectToRoute('app_index'); @@ -69,6 +72,7 @@ class SecurityController extends AbstractController if ($request->isMethod('POST')) { if (!$request->request->has('decline')) { $this->cguUserService->acceptLatestCgu($this->getUser()); + $this->loggerService->logCGUAcceptance($this->getUser()->getId()); } return $this->redirectToRoute('oauth2_authorize', $request->query->all()); @@ -83,12 +87,24 @@ class SecurityController extends AbstractController $error = $request->get('error'); $user = $this->userRepository->find($id); if (!$user) { + $this->loggerService->logEntityNotFound('User', ['user_id' => $id, + 'error' => $error ?? null, + 'message' => 'user not found for password setup'], $id); throw $this->createNotFoundException(self::NOT_FOUND); } $token = $request->get('token'); - if (empty($token) || !$this->userService->isPasswordTokenValid($user, $token)) { + if (empty($token)) { $error = 'Le lien de définition du mot de passe est invalide ou a expiré. Veuillez en demander un nouveau.'; - $this->logger->warning($user->getUserIdentifier(). " tried to use an invalid or expired password setup token."); + $this->loggerService->logTokenError('Token empty while trying to setup password', ['token' => $token, + 'token_empty' => true, + 'user_id' => $id, + 'message' => 'empty token provided for password setup']); + } + + if (!$this->userService->isPasswordTokenValid($user, $token)) { + $error = 'Le lien de définition du mot de passe est invalide ou a expiré. Veuillez en demander un nouveau.'; + $this->loggerService->logTokenError('invalid or expired token for password setup', ['user_id' => $id, + 'token' => $token,]); } return $this->render('security/password_setup.html.twig', [ 'id' => $id, @@ -102,34 +118,38 @@ class SecurityController extends AbstractController { $user = $this->userRepository->find($id); if (!$user) { + $this->loggerService->logEntityNotFound('User', ['user_id' => $id, + 'message' => 'user not found for password reset'], $id); throw $this->createNotFoundException(self::NOT_FOUND); } $newPassword = $_POST['_password'] ?? null; $confirmPassword = $_POST['_passwordConfirm'] ?? null; if ($newPassword !== $confirmPassword) { $error = 'Les mots de passe ne correspondent pas. Veuillez réessayer.'; - $this->logger->warning($user->getUserIdentifier(). " provided non-matching passwords during password reset."); + $this->loggerService->logUserAction($id, $id, 'Password confirmation does not match during password reset.'); return $this->redirectToRoute('password_setup', [ 'id' => $id, 'token' => $_POST['token'] ?? '', - 'error'=> $error]); + 'error' => $error]); } if (!$this->userService->isPasswordStrong($newPassword)) { $error = 'Le mot de passe ne respecte pas les critères de sécurité. Veuillez en choisir un autre.'; - $this->logger->warning($user->getUserIdentifier(). " provided a weak password during password reset."); - return $this->redirectToRoute('password_setup', ['id' => $id, 'token' => $_POST['token'] ?? '', 'error'=> $error]); + $this->loggerService->logUserAction($id, $id, ' provided a weak password during password reset.'); + return $this->redirectToRoute('password_setup', ['id' => $id, 'token' => $_POST['token'] ?? '', 'error' => $error]); } $this->userService->updateUserPassword($user, $newPassword); - $orgId = $this->userService->getOrgFromToken( $_POST['token']); + $this->loggerService->logUserAction($id, $id, 'Password reset user successfully.'); + $orgId = $this->userService->getOrgFromToken($_POST['token']); $uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $orgId]); - if($uo){ + if ($uo) { $uo->setStatut("ACCEPTED"); $uo->setIsActive(true); $this->entityManager->persist($uo); $this->entityManager->flush(); + $this->loggerService->logOrganizationInformation($orgId, $user->getId(), 'User accepted organization invitation during password reset.'); + $this->loggerService->logUserAction($id, $id, "User accepted organization invitation successfully with uo link id : {$uo->getId()}"); $data = ['user' => $user, 'organization' => $uo->getOrganization(), - 'ip' => $request->getClientIp(), ]; $this->organizationsService->notifyOrganizationAdmins($data, "USER_ACCEPTED"); diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index a7e6e03..be05141 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -13,6 +13,7 @@ use App\Repository\OrganizationsRepository; use App\Repository\RolesRepository; use App\Repository\UserRepository; use App\Repository\UsersOrganizationsRepository; +use App\Service\AccessTokenService; use App\Service\ActionService; use App\Service\AwsService; use App\Service\EmailService; @@ -45,17 +46,17 @@ class UserController extends AbstractController private readonly UserOrganizationService $userOrganizationService, private readonly UserRepository $userRepository, private readonly UsersOrganizationsRepository $uoRepository, - private readonly OrganizationsRepository $organizationRepository, - private readonly LoggerInterface $userManagementLogger, - private readonly LoggerInterface $organizationManagementLogger, - private readonly LoggerInterface $errorLogger, - private readonly LoggerInterface $securityLogger, - private readonly LoggerService $loggerService, - private readonly EmailService $emailService, - private readonly AwsService $awsService, - private readonly OrganizationsService $organizationsService, - private readonly AppsRepository $appsRepository, - private readonly RolesRepository $rolesRepository, + private readonly OrganizationsRepository $organizationRepository, + private readonly LoggerInterface $userManagementLogger, + private readonly LoggerInterface $organizationManagementLogger, + private readonly LoggerInterface $errorLogger, + private readonly LoggerInterface $securityLogger, + private readonly LoggerService $loggerService, + private readonly EmailService $emailService, + private readonly AwsService $awsService, + private readonly OrganizationsService $organizationsService, + private readonly AppsRepository $appsRepository, + private readonly RolesRepository $rolesRepository, private readonly AccessTokenService $accessTokenService, ) { } @@ -405,7 +406,7 @@ class UserController extends AbstractController $this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, $user); if ($this->userService->isUserConnected($user->getUserIdentifier())) { - $this->userService->revokeUserTokens($user->getUserIdentifier()); + $this->accessTokenService->revokeUserTokens($user->getUserIdentifier()); } $user->setModifiedAt(new \DateTimeImmutable('now')); @@ -563,7 +564,7 @@ class UserController extends AbstractController // Revoke tokens if connected if ($this->userService->isUserConnected($user->getUserIdentifier())) { - $this->userService->revokeUserTokens($user->getUserIdentifier()); + $this->accessTokenService->revokeUserTokens($user->getUserIdentifier()); } $this->entityManager->flush(); diff --git a/src/Service/AccessTokenService.php b/src/Service/AccessTokenService.php index 9fe9540..b36a229 100644 --- a/src/Service/AccessTokenService.php +++ b/src/Service/AccessTokenService.php @@ -11,17 +11,38 @@ class AccessTokenService private EntityManagerInterface $entityManager; - public function __construct(EntityManagerInterface $entityManager) + public function __construct(EntityManagerInterface $entityManager, + private readonly LoggerService $loggerService) { $this->entityManager = $entityManager; } - public function revokeTokens(String $userIdentifier): void { - $accessTokens = $this->entityManager->getRepository(AccessToken::class)->findBy(['userIdentifier' => $userIdentifier, 'revoked' => false]); - foreach($accessTokens as $accessToken) { - $accessToken->revoke(); - $this->entityManager->persist($accessToken); - $this->entityManager->flush(); + public function revokeUserTokens(string $userIdentifier): void + { + $tokens = $this->entityManager->getRepository(AccessToken::class)->findBy([ + 'userIdentifier' => $userIdentifier, + 'revoked' => false + ]); + foreach ($tokens as $token) { + try{ + $token->revoke(); + $this->loggerService->logTokenRevocation( + 'Access token revoked for user', + [ + 'user_identifier' => $userIdentifier, + 'token_id' => $token->getIdentifier(), + ] + ); + }catch (\Exception $e){ + $this->loggerService->logError( + 'Error revoking access token: ' . $e->getMessage(), + [ + 'user_identifier' => $userIdentifier, + 'token_id' => $token->getIdentifier(), + ] + ); + } + } } diff --git a/src/Service/LoggerService.php b/src/Service/LoggerService.php index 30ed48b..bcb05c6 100644 --- a/src/Service/LoggerService.php +++ b/src/Service/LoggerService.php @@ -78,16 +78,12 @@ readonly class LoggerService ]); } - public function logAdminNotified(int $adminUserId, int $targetUserId, int $orgId, int $actingUserId): void + public function logAdminNotified(array $array): void { - $this->emailNotificationLogger->notice('Organization admin notified', [ - 'admin_user_id' => $adminUserId, - 'target_user_id' => $targetUserId, - 'organization_id' => $orgId, - 'acting_user_id' => $actingUserId, + $this->emailNotificationLogger->notice('Organization admin notified', array_merge($array, [ 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown', 'timestamp' => $this->now(), - ]); + ])); } public function logSuperAdmin(int $userId, ?int $orgId = null, int $actingUserId, string $message): void @@ -224,4 +220,34 @@ readonly class LoggerService 'timestamp' => $this->now(), ]); } + + public function logUserConnection(string $message, array $array) + { + $this->securityLogger->info($message, array_merge($array, [ + 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown', + 'timestamp' => $this->now(), + ])); + } + + public function logCGUAcceptance(int $it) + { + $this->userManagementLogger->info("User accepted CGU", [ + 'user_id' => $it, + 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown', + 'timestamp' => $this->now(), + ]); + $this->securityLogger->info("User accepted CGU", [ + 'user_id' => $it, + 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown', + 'timestamp' => $this->now(), + ]); + } + + public function logTokenError(string $message, array $context = []): void + { + $this->securityLogger->error($message, array_merge($context, [ + 'timestamp' => $this->now(), + 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown', + ])); + } } diff --git a/src/Service/OrganizationsService.php b/src/Service/OrganizationsService.php index 142ab2e..41dc4a5 100644 --- a/src/Service/OrganizationsService.php +++ b/src/Service/OrganizationsService.php @@ -99,14 +99,11 @@ class OrganizationsService $newUser, $data['organization'] ); + $this->loggerService->logAdminNotified([ + 'admin_user_id' =>$adminUO->getUsers()->getId(), + 'target_user_id' => $newUser->getId(), + 'organization_id' => $data['organization']->getId(),'case' =>$type]); } - $this->emailNotificationLogger->info('Organization admins notified of new user accept', [ - 'admin_user_id' => $adminUO->getUsers()->getId(), - 'organization_id' => $data['organization']->getId(), - 'ip' => $data['ip'] ?? null, - 'user_accepted' => $newUser->getId(), - 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), - ]); break; case 'USER_INVITED': if ($uoa) { @@ -116,14 +113,12 @@ class OrganizationsService $invitedUser, $data['organization'] ); + $this->loggerService->logAdminNotified([ + 'admin_user_id' =>$adminUO->getUsers()->getId(), + 'target_user_id' => $newUser->getId(), + 'organization_id' => $data['organization']->getId(),'case' =>$type]); } - $this->emailNotificationLogger->info('Organization admins notified of new user invited', [ - 'admin_user_id' => $adminUO->getUsers()->getId(), - 'organization_id' => $data['organization']->getId(), - 'ip' => $data['ip'] ?? null, - 'user_accepted' => $invitedUser->getId(), - 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), - ]); + break; case 'USER_DEACTIVATED': if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { @@ -133,14 +128,12 @@ class OrganizationsService $removedUser, $data['organization'] ); + $this->loggerService->logAdminNotified([ + 'admin_user_id' =>$adminUO->getUsers()->getId(), + 'target_user_id' => $newUser->getId(), + 'organization_id' => $data['organization']->getId(),'case' =>$type]); } - $this->emailNotificationLogger->info('Organization admins notified of user deactivated', [ - 'admin_user_id' => $adminUO->getUsers()->getId(), - 'organization_id' => $data['organization']->getId(), - 'ip' => $data['ip'] ?? null, - 'user_accepted' => $removedUser->getId(), - 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), - ]); + break; case 'USER_DELETED': if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { @@ -150,14 +143,11 @@ class OrganizationsService $removedUser, $data['organization'] ); + $this->loggerService->logAdminNotified([ + 'admin_user_id' =>$adminUO->getUsers()->getId(), + 'target_user_id' => $newUser->getId(), + 'organization_id' => $data['organization']->getId(),'case' =>$type]); } - $this->emailNotificationLogger->info('Organization admins notified of user deleted', [ - 'admin_user_id' => $adminUO->getUsers()->getId(), - 'organization_id' => $data['organization']->getId(), - 'ip' => $data['ip'] ?? null, - 'user_accepted' => $removedUser->getId(), - 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), - ]); break; case 'USER_ACTIVATED': if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { @@ -167,14 +157,11 @@ class OrganizationsService $activatedUser, $data['organization'] ); + $this->loggerService->logAdminNotified([ + 'admin_user_id' =>$adminUO->getUsers()->getId(), + 'target_user_id' => $newUser->getId(), + 'organization_id' => $data['organization']->getId(),'case' =>$type]); } - $this->emailNotificationLogger->info('Organization admins notified of user activated', [ - 'admin_user_id' => $adminUO->getUsers()->getId(), - 'organization_id' => $data['organization']->getId(), - 'ip' => $data['ip'] ?? null, - 'user_accepted' => $activatedUser->getId(), - 'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM), - ]); break; } diff --git a/src/Service/UserService.php b/src/Service/UserService.php index 9f2810c..b2dee75 100644 --- a/src/Service/UserService.php +++ b/src/Service/UserService.php @@ -302,34 +302,6 @@ class UserService return 'ROLE_' . $role; } - public function revokeUserTokens(string $userIdentifier) - { - $tokens = $this->entityManager->getRepository(AccessToken::class)->findBy([ - 'userIdentifier' => $userIdentifier, - 'revoked' => false - ]); - foreach ($tokens as $token) { - try{ - $token->revoke(); - $this->loggerService->logTokenRevocation( - 'Access token revoked for user', - [ - 'user_identifier' => $userIdentifier, - 'token_id' => $token->getIdentifier(), - ] - ); - }catch (\Exception $e){ - $this->loggerService->logError( - 'Error revoking access token: ' . $e->getMessage(), - [ - 'user_identifier' => $userIdentifier, - 'token_id' => $token->getIdentifier(), - ] - ); - } - - } - } public function formatStatutForOrganizations(array $rows): array { @@ -626,13 +598,14 @@ class UserService $token = $this->generatePasswordToken($user, $org->getId()); $this->emailService->sendExistingUserNotificationEmail($user, $org, $token); $this->loggerService->logExistingUserNotificationSent($user->getId(), $org->getId()); + $this->organizationsService->notifyOrganizationAdmins(['user'=> $user, 'acting_user_id'=>$actingUser->getId(), + 'organization'=> $org], 'USER_INVITED'); } catch (\Exception $e) { $this->loggerService->logError("Error sending existing user notification: " . $e->getMessage(), [ 'target_user_id' => $user->getId(), 'organization_id' => $org->getId(), ]); } - $this->notifyOrgAdmins($user, $org, $actingUser,); } private function sendNewUserNotifications(User $user, Organizations $org, User $actingUser): void @@ -640,37 +613,14 @@ class UserService try { $token = $this->generatePasswordToken($user, $org->getId()); $this->emailService->sendPasswordSetupEmail($user, $token); + $this->organizationsService->notifyOrganizationAdmins(['user'=> $user, 'acting_user_id'=>$actingUser->getId(), + 'organization'=> $org], 'USER_INVITED'); } catch (\Exception $e) { $this->loggerService->logError("Error sending password setup email: " . $e->getMessage(), [ 'target_user_id' => $user->getId(), 'organization_id' => $org->getId(), ]); } - - $this->notifyOrgAdmins($user, $org, $actingUser); - } - - private function notifyOrgAdmins(User $user, Organizations $org, User $actingUser): void - { - try { - $data = ['user' => $user, - 'organization' => $org]; - $adminsUos = $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED'); - - foreach ($adminsUos as $adminUo) { - $this->loggerService->logAdminNotified( - $adminUo->getUsers()->getId(), - $user->getId(), - $org->getId(), - $actingUser->getId(), - ); - } - } catch (\Exception $e) { - $this->loggerService->logError("Error notifying organization admins: " . $e->getMessage(), [ - 'target_user_id' => $user->getId(), - 'organization_id' => $org->getId(), - ]); - } } }