refactor for monolog user create
This commit is contained in:
parent
cb34a18948
commit
47724734a2
|
|
@ -12,25 +12,87 @@ monolog:
|
|||
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
channels: ["!event"]
|
||||
# uncomment to get logging in your browser
|
||||
# you may have to allow bigger header sizes in your Web server configuration
|
||||
#firephp:
|
||||
# type: firephp
|
||||
# level: info
|
||||
#chromephp:
|
||||
# type: chromephp
|
||||
# level: info
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!console"]
|
||||
monolog:
|
||||
handlers:
|
||||
critical_errors:
|
||||
type: fingers_crossed
|
||||
action_level: critical
|
||||
handler: error_nested
|
||||
buffer_size: 50
|
||||
|
||||
error_nested:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/error.log"
|
||||
level: debug
|
||||
max_files: 30
|
||||
|
||||
error:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/error.log"
|
||||
level: error # logs error, critical, alert, emergency
|
||||
max_files: 30
|
||||
channels: [ error ]
|
||||
php_errors:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/php_error.log"
|
||||
level: warning # warnings, errors, fatals…
|
||||
max_files: 30
|
||||
channels: [ php ]
|
||||
# User Management
|
||||
user_management:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/user_management.log"
|
||||
level: debug
|
||||
channels: [ user_management ]
|
||||
max_files: 30
|
||||
|
||||
# Authentication
|
||||
authentication:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/authentication.log"
|
||||
level: debug
|
||||
channels: [ authentication ]
|
||||
max_files: 30
|
||||
|
||||
# Organization Management
|
||||
organization_management:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/organization_management.log"
|
||||
level: debug
|
||||
channels: [ organization_management ]
|
||||
max_files: 30
|
||||
|
||||
# Access Control
|
||||
access_control:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/access_control.log"
|
||||
level: debug
|
||||
channels: [ access_control ]
|
||||
max_files: 30
|
||||
|
||||
# Email Notifications
|
||||
email_notifications:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/email_notifications.log"
|
||||
level: debug
|
||||
channels: [ email_notifications ]
|
||||
max_files: 30
|
||||
|
||||
# Admin Actions
|
||||
admin_actions:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/admin_actions.log"
|
||||
level: debug
|
||||
channels: [ admin_actions ]
|
||||
max_files: 30
|
||||
|
||||
# Security
|
||||
security:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/security.log"
|
||||
level: debug
|
||||
channels: [ security ]
|
||||
max_files: 30
|
||||
|
||||
when@test:
|
||||
monolog:
|
||||
|
|
@ -57,7 +119,7 @@ when@prod:
|
|||
|
||||
error_nested:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/error.log"
|
||||
path: "%kernel.logs_dir%/critical.log"
|
||||
level: debug
|
||||
max_files: 30
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use App\Repository\UsersOrganizationsRepository;
|
|||
use App\Service\ActionService;
|
||||
use App\Service\AwsService;
|
||||
use App\Service\EmailService;
|
||||
use App\Service\LoggerService;
|
||||
use App\Service\OrganizationsService;
|
||||
use App\Service\UserOrganizationAppService;
|
||||
use App\Service\UserOrganizationService;
|
||||
|
|
@ -51,11 +52,8 @@ class UserController extends AbstractController
|
|||
private readonly OrganizationsRepository $organizationRepository,
|
||||
private readonly LoggerInterface $userManagementLogger,
|
||||
private readonly LoggerInterface $organizationManagementLogger,
|
||||
private readonly LoggerInterface $accessControlLogger,
|
||||
private readonly LoggerInterface $EmailNotificationLogger,
|
||||
private readonly LoggerInterface $adminActionsLogger,
|
||||
private readonly LoggerInterface $errorLogger,
|
||||
private readonly LoggerInterface $SecurityLogger,
|
||||
private readonly LoggerService $loggerService,
|
||||
private readonly EmailService $emailService,
|
||||
private readonly AwsService $awsService,
|
||||
private readonly OrganizationsService $organizationsService,
|
||||
|
|
@ -251,72 +249,130 @@ class UserController extends AbstractController
|
|||
public function new(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||
|
||||
try {
|
||||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||||
if ($this->userService->hasAccessTo($actingUser)) {
|
||||
$user = new User();
|
||||
$form = $this->createForm(UserForm::class, $user);
|
||||
$form->handleRequest($request);
|
||||
$orgId = $request->get('organizationId');
|
||||
if ($orgId){
|
||||
$org = $this->organizationRepository->find($orgId) ?? throw new NotFoundHttpException(sprintf('%s not found', $orgId));
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$existingUser = $this->userRepository->findOneBy(['email' => $user->getEmail()]);
|
||||
if ($existingUser && $orgId) {
|
||||
$this->userService->handleExistingUser($existingUser, $org);
|
||||
|
||||
$this->actionService->createAction("Create new user", $existingUser, $org, "Added user to organization" . $existingUser->getUserIdentifier() . " for organization " . $org->getName());
|
||||
$this->logger->notice("User added to organization " . $org->getName());
|
||||
$this->emailService->sendExistingUserNotificationEmail($existingUser, $org);
|
||||
$this->logger->notice("Existing user notification email sent to " . $existingUser->getUserIdentifier());
|
||||
$data = ['user' => $existingUser, 'organization' => $org];
|
||||
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
|
||||
return $this->redirectToRoute('organization_show', ['id' => $orgId]);
|
||||
}
|
||||
|
||||
|
||||
// Handle file upload
|
||||
$picture = $form->get('pictureUrl')->getData();
|
||||
$this->userService->formatNewUserData($user, $picture, true);
|
||||
|
||||
if ($orgId) {
|
||||
$uo = new UsersOrganizations();
|
||||
$uo->setUsers($user);
|
||||
$uo->setOrganization($org);
|
||||
$uo->setStatut("INVITED");
|
||||
$uo->setIsActive(false);
|
||||
$uo->setModifiedAt(new \DateTimeImmutable('now'));
|
||||
$this->entityManager->persist($uo);
|
||||
$this->actionService->createAction("Create new user", $user, $org, "Added user to organization" . $user->getUserIdentifier() . " for organization " . $org->getName());
|
||||
$this->logger->notice("User added to organization " . $org->getName());
|
||||
$this->emailService->sendPasswordSetupEmail($user, $orgId);
|
||||
$this->logger->notice("Password setup email sent to " . $user->getUserIdentifier());
|
||||
$data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()];
|
||||
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
|
||||
}
|
||||
$this->actionService->createAction("Create new user", $actingUser, null, $user->getUserIdentifier());
|
||||
$this->logger->notice("User created " . $user->getUserIdentifier());
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
if ($orgId) {
|
||||
return $this->redirectToRoute('organization_show', ['organizationId' => $orgId]);
|
||||
}
|
||||
return $this->redirectToRoute('user_index');
|
||||
}
|
||||
if (!$this->userService->hasAccessTo($actingUser)) {
|
||||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$form = $this->createForm(UserForm::class, $user);
|
||||
$form->handleRequest($request);
|
||||
|
||||
$orgId = $request->get('organizationId');
|
||||
$org = $orgId ? $this->organizationRepository->find($orgId) : null;
|
||||
|
||||
if (!$org && $orgId) {
|
||||
$this->loggerService->logCritical('Organization not found for user creation', [
|
||||
'organization_id' => $orgId,
|
||||
'acting_user_id' => $actingUser->getId(),
|
||||
'ip' => $request->getClientIp(),
|
||||
]);
|
||||
throw $this->createNotFoundException('Organization not found');
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$existingUser = $this->userRepository->findOneBy(['email' => $user->getEmail()]);
|
||||
|
||||
// Case : User exists + has organization context
|
||||
if ($existingUser && $org) {
|
||||
$this->userService->addExistingUserToOrganization(
|
||||
$existingUser,
|
||||
$org,
|
||||
$actingUser,
|
||||
$request->getClientIp()
|
||||
);
|
||||
|
||||
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
|
||||
$this->loggerService->logSuperAdmin(
|
||||
$existingUser->getId(),
|
||||
$org->getId(),
|
||||
$actingUser->getId(),
|
||||
$request->getClientIp(),
|
||||
"Super Admin linked user to organization",
|
||||
);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('organization_show', ['id' => $orgId]);
|
||||
}
|
||||
|
||||
// Case : User exists but NO organization context → error
|
||||
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(),
|
||||
]);
|
||||
|
||||
$form->get('email')->addError(
|
||||
new \Symfony\Component\Form\FormError(
|
||||
'This email is already in use. Add the user to an organization instead.'
|
||||
)
|
||||
);
|
||||
|
||||
return $this->render('user/new.html.twig', [
|
||||
'user' => $user,
|
||||
'form' => $form->createView(),
|
||||
'organizationId' => $orgId,
|
||||
]);
|
||||
}
|
||||
|
||||
$picture = $form->get('pictureUrl')->getData();
|
||||
$this->userService->createNewUser($user, $actingUser, $picture, $request->getClientIp());
|
||||
|
||||
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
|
||||
$this->loggerService->logSuperAdmin(
|
||||
$user->getId(),
|
||||
null,
|
||||
$actingUser->getId(),
|
||||
$request->getClientIp(),
|
||||
"Super Admin created new user",
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
// Link to organization if provided
|
||||
if ($org) {
|
||||
$this->userService->linkUserToOrganization(
|
||||
$user,
|
||||
$org,
|
||||
$actingUser,
|
||||
$request->getClientIp()
|
||||
);
|
||||
|
||||
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
|
||||
$this->loggerService->logSuperAdmin(
|
||||
$user->getId(),
|
||||
$org->getId(),
|
||||
$actingUser->getId(),
|
||||
$request->getClientIp(),
|
||||
"Super Admin linked user to organization",
|
||||
);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('organization_show', ['id' => $orgId]);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('user_index');
|
||||
}
|
||||
|
||||
return $this->render('user/new.html.twig', [
|
||||
'user' => $user,
|
||||
'form' => $form->createView(),
|
||||
'organizationId' => $orgId
|
||||
'user' => $user,
|
||||
'form' => $form->createView(),
|
||||
'organizationId' => $orgId,
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
if ($orgId) {
|
||||
$this->loggerService->logError("Error on user creation route: " . $e->getMessage(), [
|
||||
'ip' => $request->getClientIp(),
|
||||
]);
|
||||
|
||||
if ($orgId ?? null) {
|
||||
return $this->redirectToRoute('organization_show', ['id' => $orgId]);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('user_index');
|
||||
}
|
||||
}
|
||||
|
|
@ -730,7 +786,8 @@ class UserController extends AbstractController
|
|||
$uo->setModifiedAt(new \DateTimeImmutable());
|
||||
try {
|
||||
$data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()];
|
||||
$this->emailService->sendPasswordSetupEmail($user, $orgId);
|
||||
$token = $this->userService->generatePasswordToken($user, $org->getId());
|
||||
$this->emailService->sendPasswordSetupEmail($user, $token);
|
||||
$this->logger->info("Invitation email resent to user " . $user->getUserIdentifier() . " for organization " . $org->getName());
|
||||
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
|
||||
return $this->json(['message' => 'Invitation envoyée avec success.'], Response::HTTP_OK);
|
||||
|
|
@ -759,19 +816,20 @@ class UserController extends AbstractController
|
|||
throw $this->createNotFoundException('Invalid or expired invitation token.');
|
||||
}
|
||||
$orgId = $this->userService->getOrgFromToken($token);
|
||||
$uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $orgId]);
|
||||
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);
|
||||
throw $this->createNotFoundException('No pending invitation found for this user and organization.');
|
||||
if ($orgId) {
|
||||
$uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $orgId]);
|
||||
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);
|
||||
throw $this->createNotFoundException('No pending invitation found for this user and organization.');
|
||||
}
|
||||
$uo->setModifiedAt(new \DateTimeImmutable());
|
||||
$uo->setStatut("ACCEPTED");
|
||||
$uo->setIsActive(true);
|
||||
$this->entityManager->persist($uo);
|
||||
$this->entityManager->flush();
|
||||
$this->logger->info("User " . $user->getUserIdentifier() . " accepted invitation for organization ID " . $orgId);
|
||||
}
|
||||
$uo->setModifiedAt(new \DateTimeImmutable());
|
||||
$uo->setStatut("ACCEPTED");
|
||||
$uo->setIsActive(true);
|
||||
$this->entityManager->persist($uo);
|
||||
$this->entityManager->flush();
|
||||
$this->logger->info("User " . $user->getUserIdentifier() . " accepted invitation for organization ID " . $orgId);
|
||||
|
||||
return $this->render('user/show.html.twig', ['user' => $user, 'orgId' => $orgId]);
|
||||
return $this->render('security/login.html.twig');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,15 +14,12 @@ class EmailService
|
|||
{
|
||||
public function __construct(
|
||||
private readonly MailerInterface $mailer,
|
||||
private readonly UserService $userService,
|
||||
private readonly LoggerInterface $logger,
|
||||
private UrlGeneratorInterface $urlGenerator
|
||||
) {}
|
||||
|
||||
public function sendPasswordSetupEmail(User $user, int $orgId): void
|
||||
public function sendPasswordSetupEmail(User $user, string $token): void
|
||||
{
|
||||
$token = $this->userService->generatePasswordToken($user, $orgId);
|
||||
|
||||
// Generate absolute URL for the password setup route
|
||||
$link = $this->urlGenerator->generate(
|
||||
'password_setup',
|
||||
|
|
@ -52,9 +49,8 @@ class EmailService
|
|||
}
|
||||
}
|
||||
|
||||
public function sendExistingUserNotificationEmail(User $existingUser, Organizations $org): void
|
||||
public function sendExistingUserNotificationEmail(User $existingUser, Organizations $org, $token): void
|
||||
{
|
||||
$token = $this->userService->generatePasswordToken($existingUser, $org->getId());
|
||||
$link = $this->urlGenerator->generate('user_accept',[
|
||||
'id' => $existingUser->getId(),
|
||||
'token' => $token
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
readonly class LoggerService
|
||||
{
|
||||
public function __construct(
|
||||
private LoggerInterface $userManagementLogger,
|
||||
private LoggerInterface $organizationManagementLogger,
|
||||
private LoggerInterface $accessControlLogger,
|
||||
private LoggerInterface $emailNotificationLogger,
|
||||
private LoggerInterface $adminActionsLogger,
|
||||
private LoggerInterface $securityLogger,
|
||||
private LoggerInterface $errorLogger,
|
||||
) {}
|
||||
|
||||
// User Management Logs
|
||||
public function logUserCreated(int $userId, int $actingUserId, ?string $ip): void
|
||||
{
|
||||
$this->userManagementLogger->notice("New user created: $userId", [
|
||||
'target_user_id' => $userId,
|
||||
'acting_user_id' => $actingUserId,
|
||||
'ip' => $ip,
|
||||
'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(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Organization Management Logs
|
||||
public function logUserOrganizationLinkCreated(int $userId, int $orgId, int $actingUserId, ?int $uoId, ?string $ip): void
|
||||
{
|
||||
$this->organizationManagementLogger->notice('User-Organization link created', [
|
||||
'target_user_id' => $userId,
|
||||
'organization_id' => $orgId,
|
||||
'acting_user_id' => $actingUserId,
|
||||
'uo_id' => $uoId,
|
||||
'ip' => $ip,
|
||||
'timestamp' => $this->now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logExistingUserAddedToOrg(int $userId, int $orgId, int $actingUserId, int $uoId, ?string $ip): void
|
||||
{
|
||||
$this->organizationManagementLogger->notice('Existing user added to organization', [
|
||||
'target_user_id' => $userId,
|
||||
'organization_id' => $orgId,
|
||||
'acting_user_id' => $actingUserId,
|
||||
'uo_id' => $uoId,
|
||||
'ip' => $ip,
|
||||
'timestamp' => $this->now(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Email Notification Logs
|
||||
public function logPasswordSetupEmailSent(int $userId, ?int $orgId, ?string $ip): void
|
||||
{
|
||||
$this->emailNotificationLogger->notice("Password setup email sent to $userId", [
|
||||
'target_user_id' => $userId,
|
||||
'organization_id' => $orgId,
|
||||
'ip' => $ip,
|
||||
'timestamp' => $this->now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logExistingUserNotificationSent(int $userId, int $orgId, ?string $ip): void
|
||||
{
|
||||
$this->emailNotificationLogger->notice("Existing user notification email sent to $userId", [
|
||||
'target_user_id' => $userId,
|
||||
'organization_id' => $orgId,
|
||||
'ip' => $ip,
|
||||
'timestamp' => $this->now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logAdminNotified(int $adminUserId, int $targetUserId, int $orgId, int $actingUserId, ?string $ip): void
|
||||
{
|
||||
$this->emailNotificationLogger->notice('Organization admin notified', [
|
||||
'admin_user_id' => $adminUserId,
|
||||
'target_user_id' => $targetUserId,
|
||||
'organization_id' => $orgId,
|
||||
'acting_user_id' => $actingUserId,
|
||||
'ip' => $ip,
|
||||
'timestamp' => $this->now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logSuperAdmin(int $userId, ?int $orgId, int $actingUserId, ?string $ip, string $message): void
|
||||
{
|
||||
$this->adminActionsLogger->notice($message, [
|
||||
'target_user_id' => $userId,
|
||||
'organization_id' => $orgId,
|
||||
'acting_user_id' => $actingUserId,
|
||||
'ip' => $ip,
|
||||
'timestamp' => $this->now(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Error Logs
|
||||
public function logError(string $message, array $context = []): void
|
||||
{
|
||||
$this->errorLogger->error($message, array_merge($context, [
|
||||
'timestamp' => $this->now(),
|
||||
]));
|
||||
}
|
||||
|
||||
public function logCritical(string $message, array $context = []): void
|
||||
{
|
||||
$this->errorLogger->critical($message, array_merge($context, [
|
||||
'timestamp' => $this->now(),
|
||||
]));
|
||||
}
|
||||
|
||||
// Security Logs
|
||||
public function logAccessDenied(int $targetId, ?int $actingUserId, ?string $ip): void
|
||||
{
|
||||
$this->securityLogger->warning('Access denied', [
|
||||
'target_id' => $targetId,
|
||||
'acting_user_id' => $actingUserId,
|
||||
'ip' => $ip,
|
||||
'timestamp' => $this->now(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Helper
|
||||
private function now(): string
|
||||
{
|
||||
return (new \DateTimeImmutable('now'))->format(DATE_ATOM);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ use App\Entity\Apps;
|
|||
use App\Entity\Organizations;
|
||||
use App\Entity\Roles;
|
||||
use App\Entity\UserOrganizatonApp;
|
||||
use App\Entity\UsersOrganizations;
|
||||
use App\Repository\UsersOrganizationsRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
|
|
@ -62,7 +63,7 @@ class OrganizationsService
|
|||
}
|
||||
|
||||
|
||||
public function notifyOrganizationAdmins(array $data, string $type): void
|
||||
public function notifyOrganizationAdmins(array $data, string $type): UsersOrganizations
|
||||
{
|
||||
|
||||
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']);
|
||||
|
|
@ -130,6 +131,8 @@ class OrganizationsService
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
return $adminUO;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,14 @@ class UserService
|
|||
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager,
|
||||
private readonly Security $security,
|
||||
private readonly AwsService $awsService
|
||||
private readonly AwsService $awsService,
|
||||
private readonly LoggerService $loggerService,
|
||||
private readonly ActionService $actionService,
|
||||
private readonly EmailService $emailService,
|
||||
private readonly OrganizationsService $organizationsService,
|
||||
|
||||
|
||||
|
||||
)
|
||||
{
|
||||
|
||||
|
|
@ -330,7 +337,7 @@ class UserService
|
|||
return $formatted;
|
||||
}
|
||||
|
||||
public function generatePasswordToken(User $user, int $orgId): string
|
||||
public function generatePasswordToken(User $user, int $orgId = null): string
|
||||
{
|
||||
$orgString = "o" . $orgId . "@";
|
||||
$token = $orgString . bin2hex(random_bytes(32));
|
||||
|
|
@ -434,7 +441,7 @@ class UserService
|
|||
* @param Organizations $organization
|
||||
* @return void
|
||||
*/
|
||||
public function handleExistingUser(User $user, Organizations $organization): void
|
||||
public function handleExistingUser(User $user, Organizations $organization): int
|
||||
{
|
||||
if (!$user->isActive()) {
|
||||
$user->setIsActive(true);
|
||||
|
|
@ -448,6 +455,8 @@ class UserService
|
|||
$uo->setModifiedAt(new \DateTimeImmutable('now'));
|
||||
$this->entityManager->persist($uo);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $uo->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -479,4 +488,177 @@ class UserService
|
|||
$this->handleProfilePicture($user, $picture);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle existing user being added to an organization
|
||||
*/
|
||||
public function addExistingUserToOrganization(
|
||||
User $existingUser,
|
||||
Organizations $org,
|
||||
User $actingUser,
|
||||
?string $ip
|
||||
): int {
|
||||
try {
|
||||
$uoId = $this->handleExistingUser($existingUser, $org);
|
||||
|
||||
$this->loggerService->logExistingUserAddedToOrg(
|
||||
$existingUser->getId(),
|
||||
$org->getId(),
|
||||
$actingUser->getId(),
|
||||
$uoId,
|
||||
$ip
|
||||
);
|
||||
|
||||
$this->actionService->createAction(
|
||||
"Add existing user to organization",
|
||||
$actingUser,
|
||||
$org,
|
||||
"Added user {$existingUser->getUserIdentifier()} to {$org->getName()}"
|
||||
);
|
||||
|
||||
$this->sendExistingUserNotifications($existingUser, $org, $actingUser, $ip);
|
||||
|
||||
return $uoId;
|
||||
} catch (\Exception $e) {
|
||||
$this->loggerService->logError('Error linking existing user to organization: ' . $e->getMessage(), [
|
||||
'target_user_id' => $existingUser->getId(),
|
||||
'organization_id' => $org->getId(),
|
||||
'acting_user_id' => $actingUser->getId(),
|
||||
'ip' => $ip,
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a brand-new user
|
||||
*/
|
||||
public function createNewUser(User $user, User $actingUser, $picture, ?string $ip): void
|
||||
{
|
||||
try {
|
||||
$this->formatNewUserData($user, $picture, true);
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->loggerService->logUserCreated($user->getId(), $actingUser->getId(), $ip);
|
||||
$token = $this->generatePasswordToken($user);
|
||||
$this->emailService->sendPasswordSetupEmail($user, $token);
|
||||
$this->actionService->createAction("Create new user", $actingUser, null, $user->getUserIdentifier());
|
||||
} catch (\Exception $e) {
|
||||
$this->loggerService->logError('Error creating new user: ' . $e->getMessage(), [
|
||||
'target_user_email' => $user->getEmail(),
|
||||
'acting_user_id' => $actingUser->getId(),
|
||||
'ip' => $ip,
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link newly created user to an organization
|
||||
*/
|
||||
public function linkUserToOrganization(
|
||||
User $user,
|
||||
Organizations $org,
|
||||
User $actingUser,
|
||||
?string $ip
|
||||
): UsersOrganizations {
|
||||
try {
|
||||
$uo = new UsersOrganizations();
|
||||
$uo->setUsers($user);
|
||||
$uo->setOrganization($org);
|
||||
$uo->setStatut("INVITED");
|
||||
$uo->setIsActive(false);
|
||||
$uo->setModifiedAt(new \DateTimeImmutable('now'));
|
||||
$this->entityManager->persist($uo);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->loggerService->logUserOrganizationLinkCreated(
|
||||
$user->getId(),
|
||||
$org->getId(),
|
||||
$actingUser->getId(),
|
||||
$uo->getId(),
|
||||
$ip
|
||||
);
|
||||
|
||||
$this->actionService->createAction(
|
||||
"Link user to organization",
|
||||
$actingUser,
|
||||
$org,
|
||||
"Added {$user->getUserIdentifier()} to {$org->getName()}"
|
||||
);
|
||||
|
||||
$this->sendNewUserNotifications($user, $org, $actingUser, $ip);
|
||||
|
||||
return $uo;
|
||||
} catch (\Exception $e) {
|
||||
$this->loggerService->logError('Error linking user to organization: ' . $e->getMessage(), [
|
||||
'target_user_id' => $user->getId(),
|
||||
'organization_id' => $org->getId(),
|
||||
'acting_user_id' => $actingUser->getId(),
|
||||
'ip' => $ip,
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// Private helpers for email notifications
|
||||
private function sendExistingUserNotifications(User $user, Organizations $org, User $actingUser, ?string $ip): void
|
||||
{
|
||||
try {
|
||||
$token = $this->generatePasswordToken($user, $org->getId());
|
||||
$this->emailService->sendExistingUserNotificationEmail($user, $org, $token);
|
||||
$this->loggerService->logExistingUserNotificationSent($user->getId(), $org->getId(), $ip);
|
||||
} catch (\Exception $e) {
|
||||
$this->loggerService->logError("Error sending existing user notification: " . $e->getMessage(), [
|
||||
'target_user_id' => $user->getId(),
|
||||
'organization_id' => $org->getId(),
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->notifyOrgAdmins($user, $org, $actingUser, $ip);
|
||||
}
|
||||
|
||||
private function sendNewUserNotifications(User $user, Organizations $org, User $actingUser, ?string $ip): void
|
||||
{
|
||||
try {
|
||||
$token = $this->generatePasswordToken($user, $org->getId());
|
||||
$this->emailService->sendPasswordSetupEmail($user, $token);
|
||||
$this->loggerService->logPasswordSetupEmailSent($user->getId(), $org->getId(), $ip);
|
||||
} catch (\Exception $e) {
|
||||
$this->loggerService->logError("Error sending password setup email: " . $e->getMessage(), [
|
||||
'target_user_id' => $user->getId(),
|
||||
'organization_id' => $org->getId(),
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->notifyOrgAdmins($user, $org, $actingUser, $ip);
|
||||
}
|
||||
|
||||
private function notifyOrgAdmins(User $user, Organizations $org, User $actingUser, ?string $ip): 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(),
|
||||
$ip
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->loggerService->logError("Error notifying organization admins: " . $e->getMessage(), [
|
||||
'target_user_id' => $user->getId(),
|
||||
'organization_id' => $org->getId(),
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue