diff --git a/app/config/services_test.yaml b/app/config/services_test.yaml index e4b53ca9d..baff518f9 100644 --- a/app/config/services_test.yaml +++ b/app/config/services_test.yaml @@ -1,4 +1,5 @@ services: AppBundle\Listener\DetectClockMockingListener: autowire: true - tags: [ kernel.event_listener ] + tags: + - { name: kernel.event_listener, priority: 1000 } diff --git a/db/migrations/20260401000000_add_last_login_to_personnes_physiques.php b/db/migrations/20260401000000_add_last_login_to_personnes_physiques.php new file mode 100644 index 000000000..6eea5d345 --- /dev/null +++ b/db/migrations/20260401000000_add_last_login_to_personnes_physiques.php @@ -0,0 +1,15 @@ +table('afup_personnes_physiques') + ->addColumn('last_login', 'datetime', ['null' => true]) + ->save(); + } +} diff --git a/sources/AppBundle/Association/Model/Repository/UserRepository.php b/sources/AppBundle/Association/Model/Repository/UserRepository.php index 7fe8e663c..28fc1559a 100644 --- a/sources/AppBundle/Association/Model/Repository/UserRepository.php +++ b/sources/AppBundle/Association/Model/Repository/UserRepository.php @@ -379,7 +379,7 @@ private function getQueryBuilderWithCompleteUser() 'app.`adresse`', 'app.`code_postal`', 'app.`ville`', 'app.`id_pays`', 'app.`telephone_fixe`', 'app.`telephone_portable`', 'app.`etat`', 'app.`date_relance`', 'app.`compte_svn`', 'app.`slack_invite_status`', 'app.`slack_alternate_email`', 'app.`needs_up_to_date_membership`', - 'app.`nearest_office`', + 'app.`nearest_office`', 'app.`last_login`', 'MD5(CONCAT(app.`id`, \'_\', app.`email`, \'_\', app.`login`)) as hash', "MAX(ac.date_fin) AS lastsubcription", ]); @@ -601,6 +601,15 @@ public static function initMetadata(SerializerFactoryInterface $serializerFactor 'type' => 'bool', 'serializer' => Boolean::class, ]) + ->addField([ + 'columnName' => 'last_login', + 'fieldName' => 'lastLogin', + 'type' => 'datetime_immutable', + 'serializer_options' => [ + 'unserialize' => ['unSerializeUseFormat' => true, 'format' => 'Y-m-d H:i:s'], + 'serialize' => ['serializeUseFormat' => true, 'format' => 'Y-m-d H:i:s'], + ], + ]) ; return $metadata; diff --git a/sources/AppBundle/Association/Model/User.php b/sources/AppBundle/Association/Model/User.php index ef416d865..1444104af 100644 --- a/sources/AppBundle/Association/Model/User.php +++ b/sources/AppBundle/Association/Model/User.php @@ -148,6 +148,8 @@ class User implements NotifyPropertyInterface, NotifiableInterface, UserInterfac */ private $needsUpToDateMembership = false; + private ?\DateTimeImmutable $lastLogin = null; + /** * @return int */ @@ -503,6 +505,18 @@ public function setReminderDate(\DateTime $reminderDate = null): self return $this; } + public function getLastLogin(): ?\DateTimeImmutable + { + return $this->lastLogin; + } + + public function setLastLogin(?\DateTimeImmutable $lastLogin): self + { + $this->propertyChanged('lastLogin', $this->lastLogin, $lastLogin); + $this->lastLogin = $lastLogin; + return $this; + } + /** * @return string */ diff --git a/sources/AppBundle/Security/LoginSuccessListener.php b/sources/AppBundle/Security/LoginSuccessListener.php new file mode 100644 index 000000000..561f70d38 --- /dev/null +++ b/sources/AppBundle/Security/LoginSuccessListener.php @@ -0,0 +1,32 @@ +getAuthenticatedToken()->getUser(); + + if (!$user instanceof User) { + return; + } + + $user->setLastLogin($this->clock->now()); + $this->userRepository->save($user); + } +} diff --git a/templates/admin/members/user_edit.html.twig b/templates/admin/members/user_edit.html.twig index d90715a24..1e629eaf8 100644 --- a/templates/admin/members/user_edit.html.twig +++ b/templates/admin/members/user_edit.html.twig @@ -33,6 +33,13 @@ {% endif %} {% endif %} + + {% if user.lastLogin %} + Dernière connexion : {{ user.lastLogin|date('d/m/Y H:i:s') }} + {% else %} + Aucune connexion + {% endif %} +
diff --git a/tests/behat/features/PublicSite/Login.feature b/tests/behat/features/PublicSite/Login.feature new file mode 100644 index 000000000..ace2d4955 --- /dev/null +++ b/tests/behat/features/PublicSite/Login.feature @@ -0,0 +1,19 @@ +Feature: Page de connexion + + @reloadDbWithTestData + Scenario: Aucune connexion + Given I am logged in as admin and on the Administration + When I follow "Personnes physiques" + And I follow the button of tooltip "Modifier la fiche de Personne Paul" + Then I should see "Aucune connexion" + + @reloadDbWithTestData + Scenario: Date de dernière connexion enregistrée + Given the current date is "2026-03-10 13:56:28" + And I am logged-in with the user paul and the password paul + And I follow "Se déconnecter" + When the current date is "2026-03-20 09:12:34" + And I am logged in as admin and on the Administration + And I follow "Personnes physiques" + And I follow the button of tooltip "Modifier la fiche de Personne Paul" + Then I should see "Dernière connexion : 10/03/2026 13:56:28"