Refactor for monolog in user controller

This commit is contained in:
Charles 2025-12-08 16:27:55 +01:00
parent 659eb08d6e
commit 5f4336d824
8 changed files with 542 additions and 236 deletions

View File

@ -98,7 +98,7 @@ class SecurityController extends AbstractController
} }
#[Route('/password_reset/{id}', name: 'password_reset', methods: ['POST'])] #[Route('/password_reset/{id}', name: 'password_reset', methods: ['POST'])]
public function password_reset(int $id): Response public function password_reset(int $id, Request $request): Response
{ {
$user = $this->userRepository->find($id); $user = $this->userRepository->find($id);
if (!$user) { if (!$user) {
@ -127,7 +127,10 @@ class SecurityController extends AbstractController
$uo->setIsActive(true); $uo->setIsActive(true);
$this->entityManager->persist($uo); $this->entityManager->persist($uo);
$this->entityManager->flush(); $this->entityManager->flush();
$data = ['user' => $user, 'organization' => $uo->getOrganization()]; $data = ['user' => $user,
'organization' => $uo->getOrganization(),
'ip' => $request->getClientIp(),
];
$this->organizationsService->notifyOrganizationAdmins($data, "USER_ACCEPTED"); $this->organizationsService->notifyOrganizationAdmins($data, "USER_ACCEPTED");
} }

View File

@ -22,18 +22,14 @@ use App\Service\UserOrganizationAppService;
use App\Service\UserOrganizationService; use App\Service\UserOrganizationService;
use App\Service\UserService; use App\Service\UserService;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use mysql_xdevapi\Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
#[Route(path: '/user', name: 'user_')] #[Route(path: '/user', name: 'user_')]
class UserController extends AbstractController class UserController extends AbstractController
@ -76,6 +72,7 @@ class UserController extends AbstractController
// Vérification des droits d'accès supplémentaires // Vérification des droits d'accès supplémentaires
if (!$this->userService->hasAccessTo($actingUser)) { if (!$this->userService->hasAccessTo($actingUser)) {
$this->loggerService->logAccessDenied($actingUser->getId());
throw $this->createAccessDeniedException(self::ACCESS_DENIED); throw $this->createAccessDeniedException(self::ACCESS_DENIED);
} }
@ -104,6 +101,10 @@ class UserController extends AbstractController
]); ]);
if (!$uoList) { if (!$uoList) {
$this->loggerService->logEntityNotFound('UsersOrganization', [
'user_id' => $user->getId(),
'organization_id' => $orgId],
$actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
@ -117,6 +118,13 @@ class UserController extends AbstractController
'users' => $user, 'users' => $user,
'isActive' => true, 'isActive' => true,
]); ]);
if (!$uoList) {
$this->loggerService->logEntityNotFound('UsersOrganization', [
'user_id' => $user->getId(),
'organization_id' => $orgId],
$actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND);
}
} }
// Charger les liens UserOrganizationApp (UOA) actifs pour les UO trouvées // Charger les liens UserOrganizationApp (UOA) actifs pour les UO trouvées
@ -127,6 +135,13 @@ class UserController extends AbstractController
'userOrganization' => $uoList, 'userOrganization' => $uoList,
'isActive' => true, 'isActive' => true,
]); ]);
if (!$uoa) {
$this->loggerService->logEntityNotFound('UsersOrganizationApplication', [
'user_id' => $user->getId(),
'organization_id' => $orgId],
$actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND);
}
// Group UOA by app and ensure every app has a group // Group UOA by app and ensure every app has a group
$data['uoas'] = $this->userOrganizationAppService $data['uoas'] = $this->userOrganizationAppService
@ -171,17 +186,11 @@ class UserController extends AbstractController
{ {
$this->denyAccessUnlessGranted('ROLE_USER'); $this->denyAccessUnlessGranted('ROLE_USER');
try { try {
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
if ($this->userService->hasAccessTo($actingUser)) { if ($this->userService->hasAccessTo($actingUser)) {
$user = $this->userRepository->find($id); $user = $this->userRepository->find($id);
if (!$user) { if (!$user) {
$this->userManagementLogger->notice('User not found for edit', [ $this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId());
'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); throw $this->createNotFoundException(self::NOT_FOUND);
} }
$form = $this->createForm(UserForm::class, $user); $form = $this->createForm(UserForm::class, $user);
@ -190,37 +199,45 @@ class UserController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
// Handle user edit // Handle user edit
$picture = $form->get('pictureUrl')->getData(); $picture = $form->get('pictureUrl')->getData();
$this->userService->formatNewUserData($user, $picture); $this->userService->formatUserData($user, $picture);
$user->setModifiedAt(new \DateTimeImmutable('now')); $user->setModifiedAt(new \DateTimeImmutable('now'));
$this->entityManager->persist($user); $this->entityManager->persist($user);
$this->entityManager->flush(); $this->entityManager->flush();
//log and action //log and action
$this->userManagementLogger->notice('User information edited', [ $this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User information edited');
'target_user_id' => $user->getId(), $orgId = $request->get('organizationId');
'acting_user_id' => $actingUser->getId(), if ($orgId) {
'organization_id' => $request->get('organizationId'), $org = $this->organizationRepository->find($orgId);
'ip' => $request->getClientIp(),
'timestamp' => (new \DateTimeImmutable('now'))->format(DATE_ATOM),
]);
if ($request->get('organizationId')) {
$org = $this->organizationRepository->find($request->get('organizationId'));
if ($org) { if ($org) {
$this->actionService->createAction("Edit user information", $actingUser, $org, $user->getUserIdentifier()); $this->actionService->createAction("Edit user information", $actingUser, $org, $user->getUserIdentifier());
$this->organizationManagementLogger->info('User edited within organization context', [ $this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User information edited');
'target_user_id' => $user->getId(), if ($this->isGranted('ROLE_SUPER_ADMIN')) {
'organization_id' => $org->getId(), $this->loggerService->logSuperAdmin(
'acting_user' => $actingUser->getUserIdentifier(), $user->getId(),
'ip' => $request->getClientIp(), null,
]); $actingUser->getId(),
return $this->redirectToRoute('user_show', ['id' => $user->getId(), 'organizationId' => $request->get('organizationId')]); "Super Admin accessed user edit page",
);
}
return $this->redirectToRoute('user_show', ['id' => $user->getId(), 'organizationId' => $orgId]);
}
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND);
}
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin(
$user->getId(),
null,
$actingUser->getId(),
"Super Admin accessed user edit page",
);
} }
} else {
$this->actionService->createAction("Edit user information", $actingUser, null, $user->getUserIdentifier()); $this->actionService->createAction("Edit user information", $actingUser, null, $user->getUserIdentifier());
return $this->redirectToRoute('user_show', ['id' => $user->getId()]); return $this->redirectToRoute('user_show', ['id' => $user->getId()]);
} }
}
return $this->render('user/edit.html.twig', [ return $this->render('user/edit.html.twig', [
'user' => $user, 'user' => $user,
@ -228,26 +245,24 @@ class UserController extends AbstractController
'organizationId' => $request->get('organizationId') 'organizationId' => $request->get('organizationId')
]); ]);
} }
$this->loggerService->logAccessDenied($actingUser->getId());
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->errorLogger->critical($e->getMessage()); $this->errorLogger->critical($e->getMessage());
} }
$this->securityLogger->warning('Access denied on user edit', [ // Default deny access. shouldn't reach here normally.
'target_user_id' => $id,
'acting_user' => $actingUser?->getId(),
'ip' => $request->getClientIp(),
]);
throw $this->createAccessDeniedException(self::ACCESS_DENIED); throw $this->createAccessDeniedException(self::ACCESS_DENIED);
} }
#[Route('/new', name: 'new', methods: ['GET', 'POST'])] #[Route('/new', name: 'new', methods: ['GET', 'POST'])]
public function new(Request $request): Response public function new(Request $request): Response
{ {
$this->denyAccessUnlessGranted('ROLE_ADMIN'); $this->denyAccessUnlessGranted('ROLE_ADMIN');
try { try {
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
if (!$this->userService->hasAccessTo($actingUser)) { if (!$this->userService->hasAccessTo($actingUser)) {
$this->loggerService->logAccessDenied($actingUser->getId());
throw $this->createAccessDeniedException(self::ACCESS_DENIED); throw $this->createAccessDeniedException(self::ACCESS_DENIED);
} }
@ -256,15 +271,12 @@ class UserController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
$orgId = $request->get('organizationId'); $orgId = $request->get('organizationId');
$org = $orgId ? $this->organizationRepository->find($orgId) : null; if ($orgId) {
$org = $this->organizationRepository->find($orgId);
if (!$org && $orgId) { if (!$org) {
$this->loggerService->logCritical('Organization not found for user creation', [ $this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getId());
'organization_id' => $orgId, throw $this->createNotFoundException(self::NOT_FOUND);
'acting_user_id' => $actingUser->getId(), }
'ip' => $request->getClientIp(),
]);
throw $this->createNotFoundException('Organization not found');
} }
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
@ -276,7 +288,6 @@ class UserController extends AbstractController
$existingUser, $existingUser,
$org, $org,
$actingUser, $actingUser,
$request->getClientIp()
); );
if ($this->isGranted('ROLE_SUPER_ADMIN')) { if ($this->isGranted('ROLE_SUPER_ADMIN')) {
@ -284,20 +295,17 @@ class UserController extends AbstractController
$existingUser->getId(), $existingUser->getId(),
$org->getId(), $org->getId(),
$actingUser->getId(), $actingUser->getId(),
$request->getClientIp(),
"Super Admin linked user to organization", "Super Admin linked user to organization",
); );
} }
return $this->redirectToRoute('organization_show', ['id' => $orgId]); return $this->redirectToRoute('organization_show', ['id' => $orgId]);
} }
// Case : User exists but NO organization context → error // Case : User exists but NO organization context -> throw error on email field.
if ($existingUser) { if ($existingUser) {
$this->loggerService->logError('Attempt to create user with existing email without organization', [ $this->loggerService->logError('Attempt to create user with existing email without organization', [
'target_user_email' => $user->getid(), 'target_user_email' => $user->getid(),
'acting_user_id' => $actingUser->getId(), 'acting_user_id' => $actingUser->getId(),
'ip' => $request->getClientIp(),
]); ]);
$form->get('email')->addError( $form->get('email')->addError(
@ -314,26 +322,24 @@ class UserController extends AbstractController
} }
$picture = $form->get('pictureUrl')->getData(); $picture = $form->get('pictureUrl')->getData();
$this->userService->createNewUser($user, $actingUser, $picture, $request->getClientIp()); $this->userService->createNewUser($user, $actingUser, $picture);
if ($this->isGranted('ROLE_SUPER_ADMIN')) { if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin( $this->loggerService->logSuperAdmin(
$user->getId(), $user->getId(),
null, null,
$actingUser->getId(), $actingUser->getId(),
$request->getClientIp(),
"Super Admin created new user", "Super Admin created new user",
); );
} }
// Link to organization if provided // Case : Organization provided and user doesn't already exist
if ($org) { if ($org) {
$this->userService->linkUserToOrganization( $this->userService->linkUserToOrganization(
$user, $user,
$org, $org,
$actingUser, $actingUser,
$request->getClientIp()
); );
if ($this->isGranted('ROLE_SUPER_ADMIN')) { if ($this->isGranted('ROLE_SUPER_ADMIN')) {
@ -341,8 +347,7 @@ class UserController extends AbstractController
$user->getId(), $user->getId(),
$org->getId(), $org->getId(),
$actingUser->getId(), $actingUser->getId(),
$request->getClientIp(), "Super Admin linked user to organization during creation",
"Super Admin linked user to organization",
); );
} }
@ -359,11 +364,9 @@ class UserController extends AbstractController
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->loggerService->logError("Error on user creation route: " . $e->getMessage(), [ $this->errorLogger->critical($e->getMessage());
'ip' => $request->getClientIp(),
]);
if ($orgId ?? null) { if ($orgId) {
return $this->redirectToRoute('organization_show', ['id' => $orgId]); return $this->redirectToRoute('organization_show', ['id' => $orgId]);
} }
@ -378,32 +381,19 @@ class UserController extends AbstractController
$this->denyAccessUnlessGranted('ROLE_ADMIN'); $this->denyAccessUnlessGranted('ROLE_ADMIN');
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
$ip = $request->getClientIp();
$status = $request->get('status'); $status = $request->get('status');
try { try {
// Access control // Access control
if (!$this->userService->hasAccessTo($actingUser, true)) { if (!$this->userService->hasAccessTo($actingUser, true)) {
$this->securityLogger->warning('Access denied on user status change', [ $this->loggerService->logAccessDenied($actingUser->getId());
'target_user_id' => $id,
'acting_user_id' => $actingUser?->getId(),
'acting_identifier' => $actingUser?->getId(),
'requested_status' => $status,
'ip' => $ip,
]);
throw $this->createAccessDeniedException(self::ACCESS_DENIED); throw $this->createAccessDeniedException(self::ACCESS_DENIED);
} }
// Load target user // Load target user
$user = $this->userRepository->find($id); $user = $this->userRepository->find($id);
if (!$user) { if (!$user) {
$this->securityLogger->warning('User not found for status change', [ $this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId());
'target_user_id' => $id,
'acting_user_id' => $actingUser->getId(),
'requested_status' => $status,
'ip' => $ip,
]);
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
@ -416,31 +406,18 @@ class UserController extends AbstractController
if ($this->userService->isUserConnected($user->getUserIdentifier())) { if ($this->userService->isUserConnected($user->getUserIdentifier())) {
$this->userService->revokeUserTokens($user->getUserIdentifier()); $this->userService->revokeUserTokens($user->getUserIdentifier());
$this->securityLogger->info('User tokens revoked due to deactivation', [
'target_user_id' => $user->getId(),
'target_identifier' => $user->getId(),
'acting_user_id' => $actingUser->getId(),
'ip' => $ip,
]);
} }
$user->setModifiedAt(new \DateTimeImmutable('now')); $user->setModifiedAt(new \DateTimeImmutable('now'));
$this->entityManager->persist($user);
$this->entityManager->flush(); $this->entityManager->flush();
$this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User deactivated');
$this->userManagementLogger->notice('User deactivated', [
'target_user_id' => $user->getId(),
'target_identifier' => $user->getId(),
'acting_user_id' => $actingUser->getId(),
'ip' => $ip,
]);
if ($this->isGranted('ROLE_SUPER_ADMIN')) { if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin( $this->loggerService->logSuperAdmin(
$user->getId(), $user->getId(),
null, null,
$actingUser->getId(), $actingUser->getId(),
$ip,
'Super admin deactivated user' 'Super admin deactivated user'
); );
} }
@ -454,21 +431,16 @@ class UserController extends AbstractController
if ($status === 'activate') { if ($status === 'activate') {
$user->setIsActive(true); $user->setIsActive(true);
$user->setModifiedAt(new \DateTimeImmutable('now')); $user->setModifiedAt(new \DateTimeImmutable('now'));
$this->entityManager->persist($user);
$this->entityManager->flush(); $this->entityManager->flush();
$this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User activated');
$this->userManagementLogger->notice('User activated', [
'target_user_id' => $user->getId(),
'target_identifier' => $user->getId(),
'acting_user_id' => $actingUser->getId(),
'ip' => $ip,
]);
if ($this->isGranted('ROLE_SUPER_ADMIN')) { if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin( $this->loggerService->logSuperAdmin(
$user->getId(), $user->getId(),
null, null,
$actingUser->getId(), $actingUser->getId(),
$ip,
'Super admin activated user' 'Super admin activated user'
); );
} }
@ -479,25 +451,16 @@ class UserController extends AbstractController
} }
// Invalid status // Invalid status
$this->errorLogger->warning('Invalid status passed to activeStatus', [ $this->loggerService->logError('Invalid status provided for activeStatus', [
'target_user_id' => $user->getId(),
'acting_user_id' => $actingUser->getId(),
'requested_status' => $status, 'requested_status' => $status,
'ip' => $ip, 'target_user_id' => $id,
]); ]);
return new JsonResponse(['error' => 'Invalid status'], Response::HTTP_BAD_REQUEST); return new JsonResponse(['error' => 'Invalid status'], Response::HTTP_BAD_REQUEST);
} catch (\Throwable $e) { } catch (\Throwable $e) {
// Application-level error logging → error.log (via error channel) // Application-level error logging → error.log (via error channel)
$this->errorLogger->error('Error in activeStatus', [ $this->errorLogger->critical($e->getMessage());
'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 // Preserve 403/404 semantics, 500 for everything else
if ($e instanceof NotFoundHttpException || $e instanceof AccessDeniedException) { if ($e instanceof NotFoundHttpException || $e instanceof AccessDeniedException) {
@ -508,34 +471,152 @@ class UserController extends AbstractController
} }
} }
//TODO : MONOLOG + remove picture from bucket #[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());
try {
if ($this->userService->hasAccessTo($actingUser, true)) {
$orgId = $request->get('organizationId');
$org = $this->organizationRepository->find($orgId);
if (!$org) {
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND);
}
$user = $this->userRepository->find($id);
if (!$user) {
$this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND);
}
$uo = $this->uoRepository->findOneBy(['users' => $user,
'organization' => $org]);
if (!$uo) {
$this->loggerService->logEntityNotFound('UsersOrganization', ['user_id' => $user->getId(),
'organization_id' => $org->getId()], $actingUser->getId());
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->loggerService->logOrganizationInformation($org->getId(), $actingUser->getId(), "UO link deactivated with uo id : {$uo->getId()}");
$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->loggerService->logOrganizationInformation($orgId, $actingUser->getId(), "UO link activated with uo id : {$uo->getId()}");
$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);
}
//invalid status
$this->loggerService->logError('Invalid status provided for activateStatusOrganization', [
'requested_status' => $status,
'target_user_id' => $id,
'organization_id' => $orgId,
]);
throw $this->createNotFoundException(self::NOT_FOUND);
}
} catch (\Exception $exception) {
$this->loggerService->logCritical($exception->getMessage());
}
throw $this->createNotFoundException(self::NOT_FOUND);
}
//TODO :remove picture from bucket
#[Route('/delete/{id}', name: 'delete', methods: ['GET', 'POST'])] #[Route('/delete/{id}', name: 'delete', methods: ['GET', 'POST'])]
public function delete(int $id, Request $request): Response public function delete(int $id, Request $request): Response
{ {
$this->denyAccessUnlessGranted("ROLE_SUPER_ADMIN"); $this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN');
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
try {
$user = $this->userRepository->find($id); $user = $this->userRepository->find($id);
if (!$user) { if (!$user) {
// Security/audit log for missing user
$this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
// Soft delete the user
$user->setIsActive(false); $user->setIsActive(false);
$user->setModifiedAt(new \DateTimeImmutable('now'));
$this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, $user);
$user->setIsDeleted(true); $user->setIsDeleted(true);
if ($this->userService->isUserConnected($user)) { $user->setModifiedAt(new \DateTimeImmutable('now'));
// Deactivate all org links
$this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, $user);
$this->loggerService->logOrganizationInformation($user->getId(), $actingUser->getId(), 'All user organization links deactivated');
// Revoke tokens if connected
if ($this->userService->isUserConnected($user->getUserIdentifier())) {
$this->userService->revokeUserTokens($user->getUserIdentifier()); $this->userService->revokeUserTokens($user->getUserIdentifier());
} }
$this->entityManager->persist($user);
$this->entityManager->flush();
$this->actionService->createAction("Delete user", $actingUser, null, $user->getUserIdentifier());
$data = ['user' => $user,
'organization' => null];
$this->organizationsService->notifyOrganizationAdmins($data, "USER_DELETED");
return new Response('', Response::HTTP_NO_CONTENT); //204 $this->entityManager->flush();
// User management log
$this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User deleted');
// Super admin log (standardized style)
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin(
$user->getId(),
null,
$actingUser->getId(),
'Super admin deleted user'
);
}
$this->actionService->createAction('Delete user', $actingUser, null, $user->getUserIdentifier());
// Notify organization admins (user may belong to multiple organizations)
try {
$data = [
'user' => $user,
'organization' => null,
];
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_DELETED');
} catch (\Throwable $e) {
$this->loggerService->logCritical($e->getMessage(), [
'target_user_id' => $id,
'acting_user_id' => $actingUser?->getId(),
]);
// No rethrow here: deletion succeeded; only notifications failed
}
return new Response('', Response::HTTP_NO_CONTENT); // 204
} catch (\Exception $e) {
// Route-level error logging → error.log
$this->loggerService->logCritical('error while deleting user', [
'target_user_id' => $id,
'acting_user_id' => $actingUser?->getId(),
'error' => $e->getMessage(),
]);
if ($e instanceof NotFoundHttpException) {
throw $e; // keep 404 semantics
}
return new Response('', Response::HTTP_INTERNAL_SERVER_ERROR);
}
} }
//TODO : MONOLOG
#[Route(path: '/application/roles/{id}', name: 'application_role', methods: ['GET', 'POST'])] #[Route(path: '/application/roles/{id}', name: 'application_role', methods: ['GET', 'POST'])]
public function applicationRole(int $id, Request $request): Response public function applicationRole(int $id, Request $request): Response
{ {
@ -543,19 +624,26 @@ class UserController extends AbstractController
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
if ($this->userService->hasAccessTo($actingUser, true)) { if ($this->userService->hasAccessTo($actingUser, true)) {
$uo = $this->userOrganizationService->getByIdOrFail($id); $uo = $this->entityManager->getRepository(UsersOrganizations::class)->find($id);
if (!$uo) {
$this->loggerService->logEntityNotFound('UsersOrganization', ['id' => $id], $actingUser->getId());
throw new NotFoundHttpException("UserOrganization not found");
}
$application = $this->entityManager->getRepository(Apps::class)->find($request->get('appId')); $application = $this->entityManager->getRepository(Apps::class)->find($request->get('appId'));
if (!$application) { if (!$application) {
$this->loggerService->logEntityNotFound('Application', ['id' => $request->get('appId')], $actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
$selectedRolesIds = $request->get('roles', []); $selectedRolesIds = $request->get('roles', []);
$roleUser = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'USER']); $roleUser = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'USER']);
if (!$roleUser) { if (!$roleUser) {
throw $this->createNotFoundException('Default role not found'); $this->loggerService->logEntityNotFound('Role', ['name' => 'USER'], $actingUser->getId());
throw $this->createNotFoundException('User role not found');
} }
if (!empty($selectedRolesIds)) { if (!empty($selectedRolesIds)) {
// Si le role User n'est pas sélectionné, on désactive tous les liens (affiché comme 'accès' dans l'UI)
if (!in_array((string)$roleUser->getId(), $selectedRolesIds, true)) { if (!in_array((string)$roleUser->getId(), $selectedRolesIds, true)) {
$this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application); $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application);
} else { } else {
@ -651,6 +739,7 @@ class UserController extends AbstractController
#[Route(path: '/', name: 'index', methods: ['GET'])] #[Route(path: '/', name: 'index', methods: ['GET'])]
public function index(): Response public function index(): Response
{ {
$this->isGranted('ROLE_SUPER_ADMIN');
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
if ($this->userService->hasAccessTo($actingUser, true) && $this->isGranted("ROLE_ADMIN")) { if ($this->userService->hasAccessTo($actingUser, true) && $this->isGranted("ROLE_ADMIN")) {
$totalUsers = $this->userRepository->count(['isDeleted' => false, 'isActive' => true]); $totalUsers = $this->userRepository->count(['isDeleted' => false, 'isActive' => true]);
@ -658,6 +747,9 @@ class UserController extends AbstractController
'users' => $totalUsers 'users' => $totalUsers
]); ]);
} }
//shouldn't be reached normally
$this->loggerService->logAccessDenied($actingUser->getId());
throw $this->createAccessDeniedException(self::ACCESS_DENIED); throw $this->createAccessDeniedException(self::ACCESS_DENIED);
} }
@ -811,16 +903,21 @@ class UserController extends AbstractController
$orgId = $request->get('organizationId'); $orgId = $request->get('organizationId');
$org = $this->organizationRepository->find($orgId); $org = $this->organizationRepository->find($orgId);
if (!$org) { if (!$org) {
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
$user = $this->userRepository->find($userId); $user = $this->userRepository->find($userId);
if (!$user) { if (!$user) {
$this->loggerService->logEntityNotFound('User', ['id' => $user->getId()], $actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
$uo = $this->uoRepository->findOneBy(['users' => $user, $uo = $this->uoRepository->findOneBy(['users' => $user,
'organization' => $org, 'organization' => $org,
'statut' => "INVITED"]); 'statut' => "INVITED"]);
if (!$uo) { if (!$uo) {
$this->loggerService->logEntityNotFound('UsersOrganization', [
'user_id' => $user->getId(),
'organization_id' => $orgId], $actingUser->getId());
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
$uo->setModifiedAt(new \DateTimeImmutable()); $uo->setModifiedAt(new \DateTimeImmutable());
@ -832,7 +929,12 @@ class UserController extends AbstractController
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED'); $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
return $this->json(['message' => 'Invitation envoyée avec success.'], Response::HTTP_OK); return $this->json(['message' => 'Invitation envoyée avec success.'], Response::HTTP_OK);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error("Error resending invitation email to user " . $user->getUserIdentifier() . " for organization " . $org->getName() . ": " . $e->getMessage()); $this->loggerService->logCritical('Error while resending invitation', [
'target_user_id' => $user->getId(),
'organization_id' => $orgId,
'acting_user_id' => $actingUser->getId(),
'error' => $e->getMessage(),
]);
return $this->json(['message' => 'Erreur lors de l\'envoie du mail.'], Response::HTTP_INTERNAL_SERVER_ERROR); return $this->json(['message' => 'Erreur lors de l\'envoie du mail.'], Response::HTTP_INTERNAL_SERVER_ERROR);
} }
} }
@ -846,20 +948,35 @@ class UserController extends AbstractController
$userId = $request->get('id'); $userId = $request->get('id');
if (!$token || !$userId) { if (!$token || !$userId) {
$this->loggerService->logEntityNotFound('Token or UserId missing in accept invitation', [
'token' => $token,
'user_id' => $userId
],
null);
throw $this->createNotFoundException('Invalid invitation link.'); throw $this->createNotFoundException('Invalid invitation link.');
} }
$user = $this->userRepository->find($userId); $user = $this->userRepository->find($userId);
if (!$user) { if (!$user) {
$this->loggerService->logEntityNotFound('User not found in accept invitation', [
'user_id' => $userId
],null);
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
if (!$this->userService->isPasswordTokenValid($user, $token)) { if (!$this->userService->isPasswordTokenValid($user, $token)) {
$this->loggerService->logError('Token or UserId mismatch in accept invitation', [
'token' => $token,
'user_id' => $userId
]);
throw $this->createNotFoundException('Invalid or expired invitation token.'); throw $this->createNotFoundException('Invalid or expired invitation token.');
} }
$orgId = $this->userService->getOrgFromToken($token); $orgId = $this->userService->getOrgFromToken($token);
if ($orgId) { if ($orgId) {
$uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $orgId]); $uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $orgId]);
if (!$uo || $uo->getStatut() !== 'INVITED') { if (!$uo || $uo->getStatut() !== 'INVITED') {
$this->logger->warning("User " . $user->getUserIdentifier() . " tried to accept an invitation but no pending invitation was found for organization ID " . $orgId); $this->loggerService->logEntityNotFound('UsersOrganization not found or not in INVITED status in accept invitation', [
'user_id' => $user->getId(),
'organization_id' => $orgId
], null);
throw $this->createNotFoundException('No pending invitation found for this user and organization.'); throw $this->createNotFoundException('No pending invitation found for this user and organization.');
} }
$uo->setModifiedAt(new \DateTimeImmutable()); $uo->setModifiedAt(new \DateTimeImmutable());
@ -867,7 +984,8 @@ class UserController extends AbstractController
$uo->setIsActive(true); $uo->setIsActive(true);
$this->entityManager->persist($uo); $this->entityManager->persist($uo);
$this->entityManager->flush(); $this->entityManager->flush();
$this->logger->info("User " . $user->getUserIdentifier() . " accepted invitation for organization ID " . $orgId); $this->loggerService->logUserAction($user->getId(), null, "User accepted invitation for organization id : {$orgId}");
$this->loggerService->logOrganizationInformation($orgId, $user->getId(), "User accepted invitation with uo id : {$uo->getId()}");
} }
return $this->render('security/login.html.twig'); return $this->render('security/login.html.twig');
} }

View File

@ -4,6 +4,7 @@ namespace App\Service;
use App\Entity\Organizations; use App\Entity\Organizations;
use App\Entity\User; use App\Entity\User;
use App\Service\LoggerService;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
@ -15,7 +16,7 @@ class EmailService
public function __construct( public function __construct(
private readonly MailerInterface $mailer, private readonly MailerInterface $mailer,
private readonly LoggerInterface $logger, private readonly LoggerInterface $logger,
private UrlGeneratorInterface $urlGenerator private UrlGeneratorInterface $urlGenerator, private readonly LoggerService $loggerService
) {} ) {}
public function sendPasswordSetupEmail(User $user, string $token): void public function sendPasswordSetupEmail(User $user, string $token): void
@ -43,7 +44,9 @@ class EmailService
]); ]);
try { try {
$orgId = $this->getOrgFromToken($token);
$this->mailer->send($email); $this->mailer->send($email);
$this->loggerService->logEmailSent($user->getId(), $orgId, 'Password setup email sent.');
} catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) { } catch (\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) {
$this->logger->error('Failed to send password setup email: ' . $e->getMessage()); $this->logger->error('Failed to send password setup email: ' . $e->getMessage());
} }
@ -69,10 +72,26 @@ class EmailService
]); ]);
try{ try{
$orgId = $org->getId();
$this->loggerService->logEmailSent($existingUser->getId(), $orgId, 'Existing user notification email sent.');
$this->mailer->send($email); $this->mailer->send($email);
} catch (TransportExceptionInterface $e) { } catch (TransportExceptionInterface $e) {
$this->logger->error('Failed to send existing user notification email: ' . $e->getMessage()); $this->logger->error('Failed to send existing user notification email: ' . $e->getMessage());
} }
} }
private function getOrgFromToken(string $token): ?int
{
if (str_starts_with($token, 'o')) {
$parts = explode('@', $token);
if (count($parts) === 2) {
$orgPart = substr($parts[0], 1); // Remove the leading 'o'
if (is_numeric($orgPart)) {
return (int)$orgPart;
}
}
}
return null;
}
} }

View File

@ -4,6 +4,7 @@ namespace App\Service;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
readonly class LoggerService readonly class LoggerService
{ {
@ -15,95 +16,87 @@ readonly class LoggerService
private LoggerInterface $adminActionsLogger, private LoggerInterface $adminActionsLogger,
private LoggerInterface $securityLogger, private LoggerInterface $securityLogger,
private LoggerInterface $errorLogger, private LoggerInterface $errorLogger,
private RequestStack $requestStack,
) {} ) {}
// User Management Logs // User Management Logs
public function logUserCreated(int $userId, int $actingUserId, ?string $ip): void public function logUserCreated(int $userId, int $actingUserId): void
{ {
$this->userManagementLogger->notice("New user created: $userId", [ $this->userManagementLogger->notice("New user created: $userId", [
'target_user_id' => $userId, 'target_user_id' => $userId,
'acting_user_id' => $actingUserId, 'acting_user_id' => $actingUserId,
'ip' => $ip, 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]);
}
public function logUserEdited(int $userId, int $actingUserId, ?int $orgId, ?string $ip): void
{
$this->userManagementLogger->notice('User information edited', [
'target_user_id' => $userId,
'acting_user_id' => $actingUserId,
'organization_id' => $orgId,
'ip' => $ip,
'timestamp' => $this->now(), 'timestamp' => $this->now(),
]); ]);
} }
// Organization Management Logs // Organization Management Logs
public function logUserOrganizationLinkCreated(int $userId, int $orgId, int $actingUserId, ?int $uoId, ?string $ip): void public function logUserOrganizationLinkCreated(int $userId, int $orgId, int $actingUserId, ?int $uoId): void
{ {
$this->organizationManagementLogger->notice('User-Organization link created', [ $this->organizationManagementLogger->notice('User-Organization link created', [
'target_user_id' => $userId, 'target_user_id' => $userId,
'organization_id' => $orgId, 'organization_id' => $orgId,
'acting_user_id' => $actingUserId, 'acting_user_id' => $actingUserId,
'uo_id' => $uoId, 'uo_id' => $uoId,
'ip' => $ip, 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(), 'timestamp' => $this->now(),
]); ]);
} }
public function logExistingUserAddedToOrg(int $userId, int $orgId, int $actingUserId, int $uoId, ?string $ip): void public function logExistingUserAddedToOrg(int $userId, int $orgId, int $actingUserId, int $uoId): void
{ {
$this->organizationManagementLogger->notice('Existing user added to organization', [ $this->organizationManagementLogger->notice('Existing user added to organization', [
'target_user_id' => $userId, 'target_user_id' => $userId,
'organization_id' => $orgId, 'organization_id' => $orgId,
'acting_user_id' => $actingUserId, 'acting_user_id' => $actingUserId,
'uo_id' => $uoId, 'uo_id' => $uoId,
'ip' => $ip, 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(), 'timestamp' => $this->now(),
]); ]);
} }
// Email Notification Logs // Email Notification Logs
public function logPasswordSetupEmailSent(int $userId, ?int $orgId, ?string $ip): void public function logEmailSent(int $userId, ?int $orgId, string $message): void
{ {
$this->emailNotificationLogger->notice("Password setup email sent to $userId", [ $this->emailNotificationLogger->notice($message, [
'target_user_id' => $userId, 'target_user_id' => $userId,
'organization_id' => $orgId, 'organization_id' => $orgId,
'ip' => $ip, 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(), 'timestamp' => $this->now(),
]); ]);
} }
public function logExistingUserNotificationSent(int $userId, int $orgId, ?string $ip): void public function logExistingUserNotificationSent(int $userId, int $orgId): void
{ {
$this->emailNotificationLogger->notice("Existing user notification email sent to $userId", [ $this->emailNotificationLogger->notice("Existing user notification email sent to $userId", [
'target_user_id' => $userId, 'target_user_id' => $userId,
'organization_id' => $orgId, 'organization_id' => $orgId,
'ip' => $ip, 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(), 'timestamp' => $this->now(),
]); ]);
} }
public function logAdminNotified(int $adminUserId, int $targetUserId, int $orgId, int $actingUserId, ?string $ip): void public function logAdminNotified(int $adminUserId, int $targetUserId, int $orgId, int $actingUserId): void
{ {
$this->emailNotificationLogger->notice('Organization admin notified', [ $this->emailNotificationLogger->notice('Organization admin notified', [
'admin_user_id' => $adminUserId, 'admin_user_id' => $adminUserId,
'target_user_id' => $targetUserId, 'target_user_id' => $targetUserId,
'organization_id' => $orgId, 'organization_id' => $orgId,
'acting_user_id' => $actingUserId, 'acting_user_id' => $actingUserId,
'ip' => $ip, 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(), 'timestamp' => $this->now(),
]); ]);
} }
public function logSuperAdmin(int $userId, ?int $orgId = null, int $actingUserId, ?string $ip, string $message): void public function logSuperAdmin(int $userId, ?int $orgId = null, int $actingUserId, string $message): void
{ {
$this->adminActionsLogger->notice($message, [ $this->adminActionsLogger->notice($message, [
'target_user_id' => $userId, 'target_user_id' => $userId,
'organization_id' => $orgId, 'organization_id' => $orgId,
'acting_user_id' => $actingUserId, 'acting_user_id' => $actingUserId,
'ip' => $ip, 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(), 'timestamp' => $this->now(),
]); ]);
} }
@ -113,6 +106,7 @@ readonly class LoggerService
{ {
$this->errorLogger->error($message, array_merge($context, [ $this->errorLogger->error($message, array_merge($context, [
'timestamp' => $this->now(), 'timestamp' => $this->now(),
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
])); ]));
} }
@ -120,17 +114,18 @@ readonly class LoggerService
{ {
$this->errorLogger->critical($message, array_merge($context, [ $this->errorLogger->critical($message, array_merge($context, [
'timestamp' => $this->now(), 'timestamp' => $this->now(),
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
])); ]));
} }
// Security Logs // Security Logs
public function logAccessDenied(int $targetId, ?int $actingUserId, ?string $ip): void public function logAccessDenied(?int $actingUserId): void
{ {
$this->securityLogger->warning('Access denied', [ $this->securityLogger->warning('Access denied', [
'target_id' => $targetId,
'acting_user_id' => $actingUserId, 'acting_user_id' => $actingUserId,
'ip' => $ip, 'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(), 'timestamp' => $this->now(),
'page_accessed' => $_SERVER['REQUEST_URI'] ?? 'unknown',
]); ]);
} }
@ -139,4 +134,94 @@ readonly class LoggerService
{ {
return (new \DateTimeImmutable('now'))->format(DATE_ATOM); return (new \DateTimeImmutable('now'))->format(DATE_ATOM);
} }
public function logUserAction(int $targetId, int $actingUserId, string $message): void
{
$this->userManagementLogger->notice($message, [
'target_user_id'=> $targetId,
'acting_user_id'=> $actingUserId,
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]);
}
public function logAdminAction(int $targetId, int $actingUserId, int $organizationId, string $message): void
{
$this->adminActionsLogger->notice($message, [
'target_id' => $targetId,
'acting_user_id'=> $actingUserId,
'organization_id'=> $organizationId,
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]);
}
public function logEntityNotFound(string $entityType, array $criteria, ?int $actingUserId): void
{
$this->errorLogger->warning('Entity not found', [
'entity_type' => $entityType,
'criteria' => $criteria,
'acting_user_id' => $actingUserId,
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]);
}
public function logAWSAction(string $action, array $details): void
{
$this->securityLogger->info("AWS action performed: $action", array_merge($details, [
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]));
}
public function logTokenRevocation(string $message, array $array): void
{
$this->securityLogger->notice($message, array_merge($array, [
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]));
}
public function logUOALinkDeactivated(int $uoaId, int $appId, int $roleId): void
{
$this->securityLogger->notice('UOA link deactivated', [
'uoa_id' => $uoaId,
'app_id' => $appId,
'role_id' => $roleId,
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]);
}
public function logOrganizationInformation(int $organizationId, int $actingUserId, string $message): void
{
$this->organizationManagementLogger->info($message, [
'organization_id' => $organizationId,
'acting_user_id' => $actingUserId,
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]);
}
public function logRoleEntityAssignment(int $userId, int $organizationId, int $roleId, int $actingUserId, string $message): void
{
$this->accessControlLogger->info($message, [
'target_user_id' => $userId,
'organization_id' => $organizationId,
'role_id' => $roleId,
'acting_user_id' => $actingUserId,
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]);
}
public function logRoleAssignment(string $message, array $context): void
{
$this->accessControlLogger->info($message, [
'context' => $context,
'ip' => $this->requestStack->getCurrentRequest()?->getClientIp() ?? 'unknown',
'timestamp' => $this->now(),
]);
}
} }

View File

@ -8,7 +8,9 @@ use App\Entity\Roles;
use App\Entity\UserOrganizatonApp; use App\Entity\UserOrganizatonApp;
use App\Entity\UsersOrganizations; use App\Entity\UsersOrganizations;
use App\Repository\UsersOrganizationsRepository; use App\Repository\UsersOrganizationsRepository;
use App\Service\LoggerService;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\Exception\FileException;
class OrganizationsService class OrganizationsService
@ -19,7 +21,8 @@ class OrganizationsService
string $logoDirectory, private readonly AwsService $awsService, string $logoDirectory, private readonly AwsService $awsService,
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
private readonly UsersOrganizationsRepository $uoRepository, private readonly UsersOrganizationsRepository $uoRepository,
private readonly NotificationService $notificationService private readonly NotificationService $notificationService,
private readonly LoggerInterface $emailNotificationLogger, private readonly LoggerService $loggerService,
) )
{ {
$this->logoDirectory = $logoDirectory; $this->logoDirectory = $logoDirectory;
@ -33,8 +36,18 @@ class OrganizationsService
try { try {
$this->awsService->PutDocObj($_ENV['S3_PORTAL_BUCKET'], $logoFile, $customFilename, $extension, 'logo/'); $this->awsService->PutDocObj($_ENV['S3_PORTAL_BUCKET'], $logoFile, $customFilename, $extension, 'logo/');
$this->loggerService->logAWSAction('Upload organization logo', [
'organization_id' => $organization->getId(),
'filename' => $customFilename,
'bucket' => $_ENV['S3_PORTAL_BUCKET'],
]);
$organization->setLogoUrl('logo/' . $customFilename); $organization->setLogoUrl('logo/' . $customFilename);
} catch (FileException $e) { } catch (FileException $e) {
$this->loggerService->logError('Failed to upload organization logo to S3', [
'organization_id' => $organization->getId(),
'error' => $e->getMessage(),
'bucket' => $_ENV['S3_PORTAL_BUCKET'],
]);
throw new FileException('Failed to upload logo to S3: ' . $e->getMessage()); throw new FileException('Failed to upload logo to S3: ' . $e->getMessage());
} }
} }
@ -87,6 +100,13 @@ class OrganizationsService
$data['organization'] $data['organization']
); );
} }
$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; break;
case 'USER_INVITED': case 'USER_INVITED':
if ($uoa) { if ($uoa) {
@ -97,6 +117,13 @@ class OrganizationsService
$data['organization'] $data['organization']
); );
} }
$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; break;
case 'USER_DEACTIVATED': case 'USER_DEACTIVATED':
if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) {
@ -107,6 +134,13 @@ class OrganizationsService
$data['organization'] $data['organization']
); );
} }
$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; break;
case 'USER_DELETED': case 'USER_DELETED':
if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) {
@ -117,6 +151,13 @@ class OrganizationsService
$data['organization'] $data['organization']
); );
} }
$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; break;
case 'USER_ACTIVATED': case 'USER_ACTIVATED':
if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) { if ($uoa && $adminUO->getUsers()->getId() !== $data['user']->getId() ) {
@ -127,6 +168,13 @@ class OrganizationsService
$data['organization'] $data['organization']
); );
} }
$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; break;
} }

View File

@ -8,13 +8,15 @@ use App\Entity\User;
use App\Entity\UserOrganizatonApp; use App\Entity\UserOrganizatonApp;
use App\Entity\UsersOrganizations; use App\Entity\UsersOrganizations;
use App\Service\ActionService; use App\Service\ActionService;
use App\Service\LoggerService;
use App\Service\UserService; use App\Service\UserService;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
class UserOrganizationAppService class UserOrganizationAppService
{ {
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly ActionService $actionService, private readonly Security $security, private readonly UserService $userService) public function __construct(private readonly EntityManagerInterface $entityManager, private readonly ActionService $actionService, private readonly Security $security, private readonly UserService $userService, private readonly LoggerInterface $logger, private readonly LoggerService $loggerService)
{ {
} }
@ -79,10 +81,20 @@ class UserOrganizationAppService
$uoas = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy(['userOrganization' => $userOrganization, 'isActive' => true]); $uoas = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy(['userOrganization' => $userOrganization, 'isActive' => true]);
} }
foreach ($uoas as $uoa) { foreach ($uoas as $uoa) {
try{
$uoa->setIsActive(false); $uoa->setIsActive(false);
$this->actionService->createAction("Deactivate UOA link", $userOrganization->getUsers(), $this->actionService->createAction("Deactivate UOA link", $userOrganization->getUsers(),
$userOrganization->getOrganization(), "App: " . $uoa->getApplication()->getName() . ", Role: " . $uoa->getRole()->getName()); $userOrganization->getOrganization(), "App: " . $uoa->getApplication()->getName() . ", Role: " . $uoa->getRole()->getName());
$this->entityManager->persist($uoa); $this->entityManager->persist($uoa);
$this->loggerService->logUOALinkDeactivated($uoa->getId(), $uoa->getApplication()->getId(), $uoa->getRole()->getId());
}catch (\Exception $exception){
$this->loggerService->logCritical("Error deactivating UOA link", [
'uoa_id' => $uoa->getId(),
'app_id' => $uoa->getApplication()->getId(),
'role_id' => $uoa->getRole()->getId(),
'exception_message' => $exception->getMessage(),
]);
}
} }
} }
@ -128,6 +140,11 @@ class UserOrganizationAppService
if (!$uoa->isActive()) { if (!$uoa->isActive()) {
$uoa->setIsActive(true); $uoa->setIsActive(true);
$this->entityManager->persist($uoa); $this->entityManager->persist($uoa);
$this->loggerService->logOrganizationInformation(
$uo->getOrganization()->getId(),
$actingUser->getId(),
"Re-activated role '$roleName' for user '{$uo->getUsers()->getId()}' in application '{$application->getName()} with UOA ID {$uoa->getId()}'"
);
$this->actionService->createAction( $this->actionService->createAction(
"Re-activate user role for application", "Re-activate user role for application",
$actingUser, $actingUser,
@ -148,7 +165,11 @@ class UserOrganizationAppService
if ($uoa->isActive()) { if ($uoa->isActive()) {
$uoa->setIsActive(false); $uoa->setIsActive(false);
$this->entityManager->persist($uoa); $this->entityManager->persist($uoa);
$this->loggerService->logOrganizationInformation(
$uo->getOrganization()->getId(),
$actingUser->getId(),
"Deactivated role '$roleName' for user '{$uo->getUsers()->getId()}' in application '{$application->getName()}' with UOA ID {$uoa->getId()}'"
);
$this->actionService->createAction( $this->actionService->createAction(
"Deactivate user role for application", "Deactivate user role for application",
$actingUser, $actingUser,
@ -185,6 +206,11 @@ class UserOrganizationAppService
$this->ensureAdminRoleForSuperAdmin($newUoa); $this->ensureAdminRoleForSuperAdmin($newUoa);
} }
$this->entityManager->persist($newUoa); $this->entityManager->persist($newUoa);
$this->loggerService->logOrganizationInformation(
$uo->getOrganization()->getId(),
$actingUser->getId(),
"Created new role '{$role->getName()}' for user '{$uo->getUsers()->getId()}' in application '{$application->getName()}' with UOA ID {$newUoa->getId()}'"
);
$this->actionService->createAction("New user role for application", $this->actionService->createAction("New user role for application",
$actingUser, $actingUser,
$uo->getOrganization(), $uo->getOrganization(),

View File

@ -7,6 +7,7 @@ use App\Entity\Organizations;
use App\Entity\User; use App\Entity\User;
use App\Entity\UsersOrganizations; use App\Entity\UsersOrganizations;
use App\Service\ActionService; use App\Service\ActionService;
use App\Service\LoggerService;
use \App\Service\UserOrganizationAppService; use \App\Service\UserOrganizationAppService;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@ -19,7 +20,7 @@ readonly class UserOrganizationService
{ {
public function __construct( public function __construct(
private userOrganizationAppService $userOrganizationAppService, private EntityManagerInterface $entityManager, private ActionService $actionService, private userOrganizationAppService $userOrganizationAppService, private EntityManagerInterface $entityManager, private ActionService $actionService, private LoggerService $loggerService,
) { ) {
} }
@ -43,20 +44,14 @@ readonly class UserOrganizationService
//deactivate all UO links //deactivate all UO links
foreach ($uos as $uo) { foreach ($uos as $uo) {
$this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo); $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo);
$this->loggerService->logOrganizationInformation($uo->getOrganization()->getId(), $actingUser->getId(),
'Uo link deactivated');
$uo->setIsActive(false); $uo->setIsActive(false);
$this->entityManager->persist($uo); $this->entityManager->persist($uo);
$this->actionService->createAction("Deactivate UO link", $actingUser, $uo->getOrganization(), $uo->getOrganization()->getName() ); $this->actionService->createAction("Deactivate UO link", $actingUser, $uo->getOrganization(), $uo->getOrganization()->getName() );
} }
} }
public function getByIdOrFail(int $id): UsersOrganizations
{
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->find($id);
if (!$uo) {
throw new NotFoundHttpException("UserOrganization not found");
}
return $uo;
}
} }

View File

@ -95,6 +95,9 @@ class UserService
*/ */
public function hasAccessTo(User $user, bool $skipSelfCheck = false): bool public function hasAccessTo(User $user, bool $skipSelfCheck = false): bool
{ {
if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
return true;
}
if (!$skipSelfCheck && $user->getUserIdentifier() === $this->security->getUser()->getUserIdentifier()) { if (!$skipSelfCheck && $user->getUserIdentifier() === $this->security->getUser()->getUserIdentifier()) {
return true; return true;
} }
@ -106,9 +109,6 @@ class UserService
} }
} }
} }
if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
return true;
}
return false; return false;
} }
@ -151,6 +151,7 @@ class UserService
{ {
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $userIdentifier]); $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $userIdentifier]);
if (!$user) { if (!$user) {
$this->loggerService->logEntityNotFound('User', ['user_identifier' => $userIdentifier], null);
throw new EntityNotFoundException(self::NOT_FOUND); throw new EntityNotFoundException(self::NOT_FOUND);
} }
return $user; return $user;
@ -182,18 +183,20 @@ class UserService
return ['none' => $group]; return ['none' => $group];
} }
//TODO: reset function
public function handleProfilePicture(User $user, $picture): void public function handleProfilePicture(User $user, $picture): void
{ {
// Get file extension // Get file extension
$extension = $picture->guessExtension(); $extension = $picture->guessExtension();
// Create custom filename: userNameUserSurname_ddmmyyhhmmss // Create custom filename: userNameUserSurname_dmyHis
$customFilename = $user->getName() . $user->getSurname() . '_' . date('dmyHis') . '.' . $extension; $customFilename = $user->getName() . $user->getSurname() . '_' . date('dmyHis') . '.' . $extension;
// $customFilename = $user->getName() . $user->getSurname() . "." .$extension;
try { try {
$this->awsService->PutDocObj($_ENV['S3_PORTAL_BUCKET'], $picture, $customFilename, $extension, 'profile/'); $this->awsService->PutDocObj($_ENV['S3_PORTAL_BUCKET'], $picture, $customFilename, $extension, 'profile/');
$this->loggerService->logAWSAction(
'Profile picture uploaded to S3',[
'user_id' => $user->getId(),
'filename' => $customFilename,
]);
$user->setPictureUrl('profile/' . $customFilename); $user->setPictureUrl('profile/' . $customFilename);
} catch (FileException $e) { } catch (FileException $e) {
// Handle upload error // Handle upload error
@ -249,12 +252,18 @@ class UserService
if ($roleFormatted === 'ROLE_SUPER_ADMIN' && !in_array('ROLE_ADMIN', $user->getRoles(), true)) { if ($roleFormatted === 'ROLE_SUPER_ADMIN' && !in_array('ROLE_ADMIN', $user->getRoles(), true)) {
$user->setRoles(array_merge($user->getRoles(), ['ROLE_ADMIN'])); $user->setRoles(array_merge($user->getRoles(), ['ROLE_ADMIN']));
} }
$this->loggerService->logRoleAssignment(
'Role assigned to user',
[
'user_id' => $user->getId(),
'role' => $roleFormatted,
]
);
} else { } else {
// Remove the role if present and not used elsewhere // Remove the role if present and not used elsewhere
if (in_array($roleFormatted, $user->getRoles(), true)) { if (in_array($roleFormatted, $user->getRoles(), true)) {
$uos = $this->entityManager->getRepository(UsersOrganizations::class) $uos = $this->entityManager->getRepository(UsersOrganizations::class)
->findBy(['users' => $user, 'isActive' => true]); ->findBy(['users' => $user, 'isActive' => true]);
$hasRole = false; $hasRole = false;
foreach ($uos as $uo) { foreach ($uos as $uo) {
$uoa = $this->entityManager->getRepository(UserOrganizatonApp::class) $uoa = $this->entityManager->getRepository(UserOrganizatonApp::class)
@ -264,7 +273,6 @@ class UserService
'role' => $this->entityManager->getRepository(Roles::class) 'role' => $this->entityManager->getRepository(Roles::class)
->findOneBy(['name' => $role]), ->findOneBy(['name' => $role]),
]); ]);
if ($uoa) { if ($uoa) {
$hasRole = true; $hasRole = true;
break; break;
@ -300,9 +308,26 @@ class UserService
'userIdentifier' => $userIdentifier, 'userIdentifier' => $userIdentifier,
'revoked' => false 'revoked' => false
]); ]);
foreach ($tokens as $token) { foreach ($tokens as $token) {
try{
$token->revoke(); $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(),
]
);
}
} }
} }
@ -469,7 +494,7 @@ class UserService
* @param User $user * @param User $user
* @return void * @return void
*/ */
public function formatNewUserData(User $user, $picture, bool $setPassword = false): void public function formatUserData(User $user, $picture, bool $setPassword = false): void
{ {
// capitalize name and surname // capitalize name and surname
$user->setName(ucfirst(strtolower($user->getName()))); $user->setName(ucfirst(strtolower($user->getName())));
@ -496,7 +521,6 @@ class UserService
User $existingUser, User $existingUser,
Organizations $org, Organizations $org,
User $actingUser, User $actingUser,
?string $ip
): int { ): int {
try { try {
$uoId = $this->handleExistingUser($existingUser, $org); $uoId = $this->handleExistingUser($existingUser, $org);
@ -506,17 +530,14 @@ class UserService
$org->getId(), $org->getId(),
$actingUser->getId(), $actingUser->getId(),
$uoId, $uoId,
$ip
); );
$this->actionService->createAction( $this->actionService->createAction(
"Add existing user to organization", "Add existing user to organization",
$actingUser, $actingUser,
$org, $org,
"Added user {$existingUser->getUserIdentifier()} to {$org->getName()}" "Added user {$existingUser->getUserIdentifier()} to {$org->getName()}"
); );
$this->sendExistingUserNotifications($existingUser, $org, $actingUser);
$this->sendExistingUserNotifications($existingUser, $org, $actingUser, $ip);
return $uoId; return $uoId;
} catch (\Exception $e) { } catch (\Exception $e) {
@ -524,7 +545,6 @@ class UserService
'target_user_id' => $existingUser->getId(), 'target_user_id' => $existingUser->getId(),
'organization_id' => $org->getId(), 'organization_id' => $org->getId(),
'acting_user_id' => $actingUser->getId(), 'acting_user_id' => $actingUser->getId(),
'ip' => $ip,
]); ]);
throw $e; throw $e;
} }
@ -533,14 +553,14 @@ class UserService
/** /**
* Create a brand-new user * Create a brand-new user
*/ */
public function createNewUser(User $user, User $actingUser, $picture, ?string $ip): void public function createNewUser(User $user, User $actingUser, $picture): void
{ {
try { try {
$this->formatNewUserData($user, $picture, true); $this->formatUserData($user, $picture, true);
$this->entityManager->persist($user); $this->entityManager->persist($user);
$this->entityManager->flush(); $this->entityManager->flush();
$this->loggerService->logUserCreated($user->getId(), $actingUser->getId(), $ip); $this->loggerService->logUserCreated($user->getId(), $actingUser->getId());
$token = $this->generatePasswordToken($user); $token = $this->generatePasswordToken($user);
$this->emailService->sendPasswordSetupEmail($user, $token); $this->emailService->sendPasswordSetupEmail($user, $token);
$this->actionService->createAction("Create new user", $actingUser, null, $user->getUserIdentifier()); $this->actionService->createAction("Create new user", $actingUser, null, $user->getUserIdentifier());
@ -548,7 +568,6 @@ class UserService
$this->loggerService->logError('Error creating new user: ' . $e->getMessage(), [ $this->loggerService->logError('Error creating new user: ' . $e->getMessage(), [
'target_user_email' => $user->getEmail(), 'target_user_email' => $user->getEmail(),
'acting_user_id' => $actingUser->getId(), 'acting_user_id' => $actingUser->getId(),
'ip' => $ip,
]); ]);
throw $e; throw $e;
} }
@ -561,7 +580,6 @@ class UserService
User $user, User $user,
Organizations $org, Organizations $org,
User $actingUser, User $actingUser,
?string $ip
): UsersOrganizations { ): UsersOrganizations {
try { try {
$uo = new UsersOrganizations(); $uo = new UsersOrganizations();
@ -578,7 +596,7 @@ class UserService
$org->getId(), $org->getId(),
$actingUser->getId(), $actingUser->getId(),
$uo->getId(), $uo->getId(),
$ip
); );
$this->actionService->createAction( $this->actionService->createAction(
@ -588,7 +606,7 @@ class UserService
"Added {$user->getUserIdentifier()} to {$org->getName()}" "Added {$user->getUserIdentifier()} to {$org->getName()}"
); );
$this->sendNewUserNotifications($user, $org, $actingUser, $ip); $this->sendNewUserNotifications($user, $org, $actingUser);
return $uo; return $uo;
} catch (\Exception $e) { } catch (\Exception $e) {
@ -596,51 +614,47 @@ class UserService
'target_user_id' => $user->getId(), 'target_user_id' => $user->getId(),
'organization_id' => $org->getId(), 'organization_id' => $org->getId(),
'acting_user_id' => $actingUser->getId(), 'acting_user_id' => $actingUser->getId(),
'ip' => $ip,
]); ]);
throw $e; throw $e;
} }
} }
// Private helpers for email notifications // Private helpers for email notifications
private function sendExistingUserNotifications(User $user, Organizations $org, User $actingUser, ?string $ip): void private function sendExistingUserNotifications(User $user, Organizations $org, User $actingUser): void
{ {
try { try {
$token = $this->generatePasswordToken($user, $org->getId()); $token = $this->generatePasswordToken($user, $org->getId());
$this->emailService->sendExistingUserNotificationEmail($user, $org, $token); $this->emailService->sendExistingUserNotificationEmail($user, $org, $token);
$this->loggerService->logExistingUserNotificationSent($user->getId(), $org->getId(), $ip); $this->loggerService->logExistingUserNotificationSent($user->getId(), $org->getId());
} catch (\Exception $e) { } catch (\Exception $e) {
$this->loggerService->logError("Error sending existing user notification: " . $e->getMessage(), [ $this->loggerService->logError("Error sending existing user notification: " . $e->getMessage(), [
'target_user_id' => $user->getId(), 'target_user_id' => $user->getId(),
'organization_id' => $org->getId(), 'organization_id' => $org->getId(),
'ip' => $ip,
]); ]);
} }
$this->notifyOrgAdmins($user, $org, $actingUser,);
$this->notifyOrgAdmins($user, $org, $actingUser, $ip);
} }
private function sendNewUserNotifications(User $user, Organizations $org, User $actingUser, ?string $ip): void private function sendNewUserNotifications(User $user, Organizations $org, User $actingUser): void
{ {
try { try {
$token = $this->generatePasswordToken($user, $org->getId()); $token = $this->generatePasswordToken($user, $org->getId());
$this->emailService->sendPasswordSetupEmail($user, $token); $this->emailService->sendPasswordSetupEmail($user, $token);
$this->loggerService->logPasswordSetupEmailSent($user->getId(), $org->getId(), $ip);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->loggerService->logError("Error sending password setup email: " . $e->getMessage(), [ $this->loggerService->logError("Error sending password setup email: " . $e->getMessage(), [
'target_user_id' => $user->getId(), 'target_user_id' => $user->getId(),
'organization_id' => $org->getId(), 'organization_id' => $org->getId(),
'ip' => $ip,
]); ]);
} }
$this->notifyOrgAdmins($user, $org, $actingUser, $ip); $this->notifyOrgAdmins($user, $org, $actingUser);
} }
private function notifyOrgAdmins(User $user, Organizations $org, User $actingUser, ?string $ip): void private function notifyOrgAdmins(User $user, Organizations $org, User $actingUser): void
{ {
try { try {
$data = ['user' => $user, 'organization' => $org]; $data = ['user' => $user,
'organization' => $org];
$adminsUos = $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED'); $adminsUos = $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
foreach ($adminsUos as $adminUo) { foreach ($adminsUos as $adminUo) {
@ -649,14 +663,12 @@ class UserService
$user->getId(), $user->getId(),
$org->getId(), $org->getId(),
$actingUser->getId(), $actingUser->getId(),
$ip
); );
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->loggerService->logError("Error notifying organization admins: " . $e->getMessage(), [ $this->loggerService->logError("Error notifying organization admins: " . $e->getMessage(), [
'target_user_id' => $user->getId(), 'target_user_id' => $user->getId(),
'organization_id' => $org->getId(), 'organization_id' => $org->getId(),
'ip' => $ip,
]); ]);
} }
} }