entityManager->getRepository(AccessToken::class)->findBy([ 'userIdentifier' => $userIdentifier, 'revoked' => false ]); foreach ($tokens as $token) { // Assuming $token->getExpiry() returns a DateTimeInterface if ($token->getExpiry() > $now) { return true; } } return false; } /** * Check if the user have the rights to access the page * Self check can be skipped when checking access for the current user * * @param User $user * @param bool $skipSelfCheck * @return bool * @throws Exception */ public function hasAccessTo(User $user, bool $skipSelfCheck = false): bool { if (!$skipSelfCheck && $user->getUserIdentifier() === $this->security->getUser()->getUserIdentifier()) { return true; } $userOrganizations = $this->entityManager->getRepository(UsersOrganizations::class)->findBy(['users' => $user]); if ($userOrganizations) { foreach ($userOrganizations as $uo) { if ($this->isAdminOfOrganization($uo->getOrganization())) { return true; } } } if ($this->security->isGranted('ROLE_SUPER_ADMIN')) { return true; } return false; } /** * Check if the user is an admin of the organization * A user is considered an admin of an organization if they have the 'ROLE_ADMIN' AND have the link to the * entity role 'ROLE_ADMIN' in the UsersOrganizationsApp entity * (if he is admin for any application of the organization). * * @param UsersOrganizations $usersOrganizations * @return bool * @throws Exception */ public function isAdminOfOrganization(Organizations $organizations): bool { $actingUser = $this->getUserByIdentifier($this->security->getUser()->getUserIdentifier()); $uo = $this->entityManager->getRepository(UsersOrganizations::class)->findOneBy(['users' => $actingUser, 'organization' => $organizations]); $roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']); if ($uo) { $uoa = $this->entityManager->getRepository(UserOrganizatonApp::class)->findOneBy(['userOrganization' => $uo, 'role' => $roleAdmin, 'isActive' => true]); if ($uoa && $this->security->isGranted('ROLE_ADMIN')) { return true; } } return false; } /** * Get the user by their identifier. * * @param string $userIdentifier * @return User|null * @throws Exception */ public function getUserByIdentifier(string $userIdentifier): ?User { $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $userIdentifier]); if (!$user) { throw new EntityNotFoundException(self::NOT_FOUND); } return $user; } /** * Format users without organization for admin view. * * @param array $users * @return array */ public function formatNoOrgUsersAsAssoc(array $noOrgUsers): array { $group = [ 'id' => null, 'name' => 'Utilisateurs', 'users' => [], ]; foreach ($noOrgUsers as $user) { $group['users'][] = [ 'entity' => $user, 'connected' => $this->isUserConnected($user->getUserIdentifier()), ]; } // Use a fixed key (e.g., 0 or 'none') to avoid collisions with real org IDs return ['none' => $group]; } //TODO: reset function public function handleProfilePicture(User $user, $picture): void { // Get file extension $extension = $picture->guessExtension(); // Create custom filename: userNameUserSurname_ddmmyyhhmmss $customFilename = $user->getName() . $user->getSurname() . '_' . date('dmyHis') . '.' . $extension; // $customFilename = $user->getName() . $user->getSurname() . "." .$extension; try { $this->awsService->PutDocObj($_ENV['S3_PORTAL_BUCKET'], $picture, $customFilename, $extension, 'profile/'); $user->setPictureUrl('profile/' . $customFilename); } catch (FileException $e) { // Handle upload error throw new FileException('File upload failed: ' . $e->getMessage()); } // // } /** * Format users of a specific organization. * * @param UsersOrganizations[] $userOrganizations * @return array */ public function formatOrgUsers(array $userOrganizations): array { $users = []; foreach ($userOrganizations as $uo) { $user = $uo->getUsers(); $users[] = [ 'entity' => $user, 'connected' => $this->isUserConnected($user->getUserIdentifier()), 'isActive' => (bool)$uo->isActive(), ]; } return $users; } /** * Handle user's role synchronization * * @param User $user * @param string $role * @param boolean $add * @return void */ public function syncUserRoles(User $user, string $role, bool $add): void { $roleFormatted = $this->formatRoleString($role); if ($add) { // Add the main role if not already present if (!in_array($roleFormatted, $user->getRoles(), true)) { $user->setRoles(array_merge($user->getRoles(), [$roleFormatted])); } // If SUPER ADMIN is given → ensure ADMIN is also present if ($roleFormatted === 'ROLE_SUPER_ADMIN' && !in_array('ROLE_ADMIN', $user->getRoles(), true)) { $user->setRoles(array_merge($user->getRoles(), ['ROLE_ADMIN'])); } } else { // Remove the role if present and not used elsewhere if (in_array($roleFormatted, $user->getRoles(), true)) { $uos = $this->entityManager->getRepository(UsersOrganizations::class) ->findBy(['users' => $user, 'isActive' => true]); $hasRole = false; foreach ($uos as $uo) { $uoa = $this->entityManager->getRepository(UserOrganizatonApp::class) ->findBy([ 'userOrganization' => $uo, 'isActive' => true, 'role' => $this->entityManager->getRepository(Roles::class) ->findOneBy(['name' => $role]), ]); if ($uoa) { $hasRole = true; break; } } // Only remove globally if no other app gives this role if (!$hasRole) { $roles = $user->getRoles(); $roles = array_filter($roles, fn($r) => $r !== $roleFormatted); $user->setRoles($roles); } } } } /** * Format role string to match the ROLE_ convention */ public function formatRoleString(string $role): string { $role = str_replace(' ', '_', trim($role)); $role = strtoupper($role); if (str_starts_with($role, 'ROLE_')) { return $role; } return 'ROLE_' . $role; } public function revokeUserTokens(string $userIdentifier) { $tokens = $this->entityManager->getRepository(AccessToken::class)->findBy([ 'userIdentifier' => $userIdentifier, 'revoked' => false ]); foreach ($tokens as $token) { $token->revoke(); } } public function formatStatutForOrganizations(array $rows): array { $formatted = array_map(function (UsersOrganizations $uo) { $user = $uo->getUsers(); $picture = $this->awsService->getPublicUrl($_ENV['S3_PORTAL_BUCKET']) . $user->getPictureUrl(); if ($uo->getStatut() === "INVITED") { $statut = "INVITED"; // if user invited but not accepted in 1 hour, set statut to EXPIRED $now = new DateTimeImmutable(); $invitationTime = $uo->getModifiedAt(); $expiryTime = $invitationTime->modify('+1 hour'); if ($now > $expiryTime) { $statut = "EXPIRED"; } } else { $statut = $uo->isActive() ? "ACTIVE" : "INACTIVE"; } return [ 'pictureUrl' => $picture, 'name' => $user->getSurname(), 'prenom' => $user->getName(), 'email' => $user->getEmail(), 'isConnected' => $this->isUserConnected($user->getUserIdentifier()), 'statut' => $statut, 'showUrl' => '/user/view/' . $user->getId() . '?organizationId=' . $uo->getOrganization()->getId(), 'id' => $user->getId(), ]; }, $rows); return $formatted; } public function generatePasswordToken(User $user, int $orgId): string { $orgString = "o" . $orgId . "@"; $token = $orgString . bin2hex(random_bytes(32)); $user->setPasswordToken($token); $user->setTokenExpiry(new DateTimeImmutable('+1 hour', new \DateTimeZone('Europe/Paris'))); $this->entityManager->persist($user); $this->entityManager->flush(); return $token; } public function isPasswordTokenValid(User $user, string $token): bool { if ($user->getPasswordToken() !== $token || $user->getTokenExpiry() < new DateTimeImmutable()) { return false; } return true; } public function isPasswordStrong(string $newPassword): bool { $pewpew = 0; if (preg_match('/\w/', $newPassword)) { //Find any alphabetical letter (a to Z) and digit (0 to 9) $pewpew++; } if (preg_match('/\W/', $newPassword)) { //Find any non-alphabetical and non-digit character $pewpew++; } if (strlen($newPassword) > 8) { $pewpew++; } return $pewpew >= 3; } public function updateUserPassword(User $user, string $newPassword): void { $user->setPassword(password_hash($newPassword, PASSWORD_BCRYPT)); $user->setModifiedAt(new DateTimeImmutable('now', new DateTimeZone('Europe/Paris'))); $user->setPasswordToken(null); $user->setTokenExpiry(null); $this->entityManager->persist($user); $this->entityManager->flush(); } public 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; } /** * Get roles array for a user, optionally including super admin roles. * ViewSAdminRoles flag determines if super admin roles should be included. * * @param User $actingUser * @param bool $viewSAdminRoles * @return array */ public function getRolesArrayForUser(User $actingUser, bool $viewSAdminRoles = false): array { $roles = $this->entityManager->getRepository(Roles::class)->findAll(); $rolesArray = []; foreach ($roles as $role) { if (!$viewSAdminRoles && $role->getName() === 'SUPER ADMIN') { continue; } $rolesArray[] = [ 'id' => $role->getId(), 'name' => $role->getName(), ]; } return $rolesArray; } public function canEditRolesCheck(User $actingUser, User $user, $org, bool $isAdmin): bool { $userRoles = $user->getRoles(); $actingUserRoles = $actingUser->getRoles(); // if acting user is admin, he can´t edit super admin roles if (in_array('ROLE_SUPER_ADMIN', $userRoles, true) && !in_array('ROLE_SUPER_ADMIN', $actingUserRoles, true)) { return false; } return $isAdmin && !empty($org); } /** * Handle already existing user when creating a user. * If the user exists but is inactive, reactivate them. * * @param User $user * @param Organizations $organization * @return void */ public function handleExistingUser(User $user, Organizations $organization): void { if (!$user->isActive()) { $user->setIsActive(true); $this->entityManager->persist($user); } $uo = new UsersOrganizations(); $uo->setUsers($user); $uo->setOrganization($organization); $uo->setStatut("INVITED"); $uo->setIsActive(false); $uo->setModifiedAt(new \DateTimeImmutable('now')); $this->entityManager->persist($uo); $this->entityManager->flush(); } /** * Format new user data. * Capitalize name and surname. * Trim strings. * Set random password * Handle picture if provided * * @param User $user * @return void */ public function formatNewUserData(User $user, $picture): void { // capitalize name and surname $user->setName(ucfirst(strtolower($user->getName()))); $user->setSurname(ucfirst(strtolower($user->getSurname()))); // trim strings $user->setName(trim($user->getName())); $user->setSurname(trim($user->getSurname())); $user->setEmail(trim($user->getEmail())); //FOR SETTING A DEFAULT RANDOM PASSWORD OF 50 CHARACTERS until user set his own password $user->setPassword($this->generateRandomPassword()); if($picture) { $this->handleProfilePicture($user, $picture); } } }