Repository: opfab/operatorfabric-core
Pull request #8432: Use the e-mail address from the Keycloak account (#8392)
Author: login: vlo-rte
Created: 2025-05-26T13:56:12Z | Merged: 2025-05-28T15:13:19Z

- In release notes :
  -  In chapter : Features
  -  Text : #8392 : Use the e-mail address from the Keycloak account 


Diff:
diff --git a/config/docker/ui-config/web-ui.json b/config/docker/ui-config/web-ui.json
index 3e02f8d96e..2e910b42ef 100644
--- a/config/docker/ui-config/web-ui.json
+++ b/config/docker/ui-config/web-ui.json
@@ -128,7 +128,8 @@
     "dateFormat": "dd/MM/yyyy",
     "styleWhenNightDayModeDesactivated": "NIGHT",
     "replayInterval": 10,
-    "replayEnabled": true
+    "replayEnabled": true,
+    "getEmailFromUserInsteadOfSettings": false
   },
   "settingsScreen": {
     "hiddenSettings": []
diff --git a/config/docker/users.yml b/config/docker/users.yml
index fbf50d50c0..b6bc3d4006 100644
--- a/config/docker/users.yml
+++ b/config/docker/users.yml
@@ -1,167 +1,170 @@
 #  POPULATE THE USER DATABASE ON INIT
 # !!!! WARNING: VALUES SHOULD BE CHANGED FOR PRODUCTION MODE !!!!
 #
-operatorfabric.users.default:
-  users:
-    - login: admin
-      groups: ["ADMIN"]
-      entities: ["ENTITY1_FR","ENTITY2_FR"]
-    - login: operator1_fr
-      firstname : John
-      lastname : Doe
-      groups: ["Dispatcher","Maintainer"]
-      entities: ["ENTITY1_FR"]
-    - login: operator2_fr
-      groups: ["Planner", "Maintainer", "Manager"]
-      entities: ["ENTITY2_FR"]
-    - login: operator3_fr
-      groups: [ "RTE","ADMIN","Maintainer","Dispatcher"]
-      entities: [ "ENTITY3_FR" ]
-    - login: operator4_fr
-      groups: ["Maintainer","Dispatcher"]
-      entities: ["ENTITY1_FR", "ENTITY2_FR", "ENTITY3_FR",  "ENTITY4_FR" ]
-    - login: operator5_fr
-      groups: [ "RTE","ADMIN","Maintainer","Dispatcher"]
-      entities: [ "ENTITY1_FR" ]
-    - login: operator6_fr
-      groups: ["Maintainer"]
-      entities: ["ENTITY1_FR"]
-    - login: itsupervisor1
-      groups: ["ADMIN", "Supervisor"]
-      entities: ["IT_SUPERVISOR_ENTITY"]
-    - login: operator1_crisisRoom
-      groups: ["Supervisor"]
-      entities: ["ENTITY1_FR", "ENTITY2_FR", "ENTITY3_FR", "ENTITY4_FR"]
-    - login: operator1_it
-      groups: ["Dispatcher", "Maintainer"]
-      entities: ["ENTITY1_IT"]
-    - login: operator2_it
-      groups: ["Planner", "Maintainer"]
-      entities: ["ENTITY2_IT"]
-    - login: operator3_it
-      groups: ["Dispatcher", "Planner", "Maintainer"]
-      entities: ["ENTITY3_IT"]
-    - login: operator1_nl
-      groups: ["Dispatcher", "Planner", "Maintainer"]
-      entities: ["ENTITY1_NL"]
-    - login: operator2_nl
-      groups: ["Dispatcher", "Planner", "Maintainer"]
-      entities: ["ENTITY2_NL"]
-    - login: operator1_eu
-      groups: ["Planner", "Maintainer"]
-      entities: ["ENTITY1_EU"]
-    - login: operator2_eu
-      groups: ["Planner", "Maintainer"]
-      entities: ["ENTITY2_EU"]
-    - login: publisher_test
-      groups: ["Dispatcher", "Maintainer", "Manager"]
-    - login: opfab
-      groups: ["Dispatcher", "Maintainer", "Manager","ADMIN"]
-  groups:
-    - id: ADMIN
-      name: ADMINISTRATORS
-      description: The admin group
-      permissions: ["ADMIN"]
-    - id: RTE
-      name: RTE France
-      description: RTE TSO Group
-      realtime: false
-    - id: Dispatcher
-      name: Dispatcher
-      description: Dispatcher Group
-      realtime: true
-    - id: Planner
-      name: Planner
-      description: Planner Group
-      realtime: true
-    - id: Supervisor
-      name: Supervisor
-      description: Supervisor Group
-      realtime: true
-      permissions: ["READONLY","ADMIN_BUSINESS_PROCESS","VIEW_ALL_CARDS"]
-    - id: Manager
-      name: Manager
-      description: Manager Group
-      realtime: false
-      permissions: ["VIEW_ALL_CARDS"]
-    - id: Maintainer
-      name: Maintainer
-      description: Maintainer Group
-      realtime: false
-  entities:
-   - id: ENTITY1_FR
-     name: Control Center FR North
-     description: Control Center FR North
-     parents : ["ENTITY_FR"]
-     labels: ["FR1 label"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY2_FR
-     name: Control Center FR South
-     description: Control Center FR South
-     parents : ["ENTITY_FR"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY3_FR
-     name: Control Center FR East
-     description: Control Center FR East
-     parents : ["ENTITY_FR"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY4_FR
-     name: Control Center FR West
-     description: Control Center FR West
-     parents : ["ENTITY_FR"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY_FR
-     name: French Control Centers
-     description: French Control Centers
-     roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
-   - id: EUROPEAN_SUPERVISION_CENTERS
-     name: European Supervision Centers
-     description: European Supervision Centers
-     roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
-   - id: IT_SUPERVISOR_ENTITY
-     name: IT SUPERVISION CENTER
-     description: IT SUPERVISION CENTER
-     parents : ["EUROPEAN_SUPERVISION_CENTERS"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY1_IT
-     name: Control Center IT North
-     description: Control Center IT North
-     parents : ["ENTITY_IT"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY2_IT
-     name: Control Center IT Center
-     description: Control Center IT Center
-     parents : ["ENTITY_IT"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY3_IT
-     name: Control Center IT South
-     description: Control Center IT South
-     parents : ["ENTITY_IT"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY_IT
-     name: Italian Control Centers
-     description: Italian Control Centers
-     roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
-   - id: ENTITY1_NL
-     name: Control Center NL North
-     description: Control Center NL North
-     parents : ["ENTITY_NL"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY2_NL
-     name: Control Center NL South
-     description: Control Center NL South
-     parents : ["ENTITY_NL"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY_NL
-     name: Dutch Control Centers
-     description: Dutch Control Centers
-     roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
-   - id: ENTITY1_EU
-     name: North Europe Control Center
-     description: North Europe Control Center
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY2_EU
-     name: South Europe Control Center
-     description: South Europe Control Center
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+
+operatorfabric.users:
+  getEmailFromUserInsteadOfSettings: false
+  default:
+    users:
+      - login: admin
+        groups: ["ADMIN"]
+        entities: ["ENTITY1_FR","ENTITY2_FR"]
+      - login: operator1_fr
+        firstname : John
+        lastname : Doe
+        groups: ["Dispatcher","Maintainer"]
+        entities: ["ENTITY1_FR"]
+      - login: operator2_fr
+        groups: ["Planner", "Maintainer", "Manager"]
+        entities: ["ENTITY2_FR"]
+      - login: operator3_fr
+        groups: [ "RTE","ADMIN","Maintainer","Dispatcher"]
+        entities: [ "ENTITY3_FR" ]
+      - login: operator4_fr
+        groups: ["Maintainer","Dispatcher"]
+        entities: ["ENTITY1_FR", "ENTITY2_FR", "ENTITY3_FR",  "ENTITY4_FR" ]
+      - login: operator5_fr
+        groups: [ "RTE","ADMIN","Maintainer","Dispatcher"]
+        entities: [ "ENTITY1_FR" ]
+      - login: operator6_fr
+        groups: ["Maintainer"]
+        entities: ["ENTITY1_FR"]
+      - login: itsupervisor1
+        groups: ["ADMIN", "Supervisor"]
+        entities: ["IT_SUPERVISOR_ENTITY"]
+      - login: operator1_crisisRoom
+        groups: ["Supervisor"]
+        entities: ["ENTITY1_FR", "ENTITY2_FR", "ENTITY3_FR", "ENTITY4_FR"]
+      - login: operator1_it
+        groups: ["Dispatcher", "Maintainer"]
+        entities: ["ENTITY1_IT"]
+      - login: operator2_it
+        groups: ["Planner", "Maintainer"]
+        entities: ["ENTITY2_IT"]
+      - login: operator3_it
+        groups: ["Dispatcher", "Planner", "Maintainer"]
+        entities: ["ENTITY3_IT"]
+      - login: operator1_nl
+        groups: ["Dispatcher", "Planner", "Maintainer"]
+        entities: ["ENTITY1_NL"]
+      - login: operator2_nl
+        groups: ["Dispatcher", "Planner", "Maintainer"]
+        entities: ["ENTITY2_NL"]
+      - login: operator1_eu
+        groups: ["Planner", "Maintainer"]
+        entities: ["ENTITY1_EU"]
+      - login: operator2_eu
+        groups: ["Planner", "Maintainer"]
+        entities: ["ENTITY2_EU"]
+      - login: publisher_test
+        groups: ["Dispatcher", "Maintainer", "Manager"]
+      - login: opfab
+        groups: ["Dispatcher", "Maintainer", "Manager","ADMIN"]
+    groups:
+      - id: ADMIN
+        name: ADMINISTRATORS
+        description: The admin group
+        permissions: ["ADMIN"]
+      - id: RTE
+        name: RTE France
+        description: RTE TSO Group
+        realtime: false
+      - id: Dispatcher
+        name: Dispatcher
+        description: Dispatcher Group
+        realtime: true
+      - id: Planner
+        name: Planner
+        description: Planner Group
+        realtime: true
+      - id: Supervisor
+        name: Supervisor
+        description: Supervisor Group
+        realtime: true
+        permissions: ["READONLY","ADMIN_BUSINESS_PROCESS","VIEW_ALL_CARDS"]
+      - id: Manager
+        name: Manager
+        description: Manager Group
+        realtime: false
+        permissions: ["VIEW_ALL_CARDS"]
+      - id: Maintainer
+        name: Maintainer
+        description: Maintainer Group
+        realtime: false
+    entities:
+     - id: ENTITY1_FR
+       name: Control Center FR North
+       description: Control Center FR North
+       parents : ["ENTITY_FR"]
+       labels: ["FR1 label"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY2_FR
+       name: Control Center FR South
+       description: Control Center FR South
+       parents : ["ENTITY_FR"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY3_FR
+       name: Control Center FR East
+       description: Control Center FR East
+       parents : ["ENTITY_FR"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY4_FR
+       name: Control Center FR West
+       description: Control Center FR West
+       parents : ["ENTITY_FR"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY_FR
+       name: French Control Centers
+       description: French Control Centers
+       roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
+     - id: EUROPEAN_SUPERVISION_CENTERS
+       name: European Supervision Centers
+       description: European Supervision Centers
+       roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
+     - id: IT_SUPERVISOR_ENTITY
+       name: IT SUPERVISION CENTER
+       description: IT SUPERVISION CENTER
+       parents : ["EUROPEAN_SUPERVISION_CENTERS"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY1_IT
+       name: Control Center IT North
+       description: Control Center IT North
+       parents : ["ENTITY_IT"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY2_IT
+       name: Control Center IT Center
+       description: Control Center IT Center
+       parents : ["ENTITY_IT"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY3_IT
+       name: Control Center IT South
+       description: Control Center IT South
+       parents : ["ENTITY_IT"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY_IT
+       name: Italian Control Centers
+       description: Italian Control Centers
+       roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
+     - id: ENTITY1_NL
+       name: Control Center NL North
+       description: Control Center NL North
+       parents : ["ENTITY_NL"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY2_NL
+       name: Control Center NL South
+       description: Control Center NL South
+       parents : ["ENTITY_NL"]
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY_NL
+       name: Dutch Control Centers
+       description: Dutch Control Centers
+       roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
+     - id: ENTITY1_EU
+       name: North Europe Control Center
+       description: North Europe Control Center
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+     - id: ENTITY2_EU
+       name: South Europe Control Center
+       description: South Europe Control Center
+       roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
 #logging.level.org.opfab: debug
\ No newline at end of file
diff --git a/config/keycloak/export/dev-users-0.json b/config/keycloak/export/dev-users-0.json
index ce7a26503d..d8e2476ac7 100644
--- a/config/keycloak/export/dev-users-0.json
+++ b/config/keycloak/export/dev-users-0.json
@@ -217,6 +217,7 @@
     "emailVerified" : false,
     "firstName" : "",
     "lastName" : "",
+    "email" : "operator2_fr@test.com",
     "attributes" : {
       "groups" : [ "Planner;Maintainer;Manager" ],
       "entitiesId" : [ "ENTITY2_FR;IT_SUPERVISOR_ENTITY" ]
diff --git a/services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java b/services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java
index 95131b196f..3412527b7e 100644
--- a/services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java
+++ b/services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java
@@ -122,11 +122,12 @@ private User createUserDataVirtualFromJwt(Jwt jwt) {
                 String givenName = extractClaimAsStringOrNull(jwt, jwtProperties.getGivenNameClaim());
                 String familyName = extractClaimAsStringOrNull(jwt, jwtProperties.getFamilyNameClaim());
                 String name = extractClaimAsStringOrNull(jwt, jwtProperties.getNameClaim());
+                String email = extractClaimAsStringOrNull(jwt, jwtProperties.getEmailClaim());
 
                 if (givenName == null && familyName == null)
                     familyName = name;
 
-                return new User(principalId, givenName, familyName, null, null, null);
+                return new User(principalId, givenName, familyName, email, null, null, null);
             }
 
             /**
diff --git a/services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java b/services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java
index 6103cbda40..fff070918a 100644
--- a/services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java
+++ b/services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java
@@ -1,4 +1,4 @@
-/* Copyright (c) 2018-2024, RTE (http://www.rte-france.com)
+/* Copyright (c) 2018-2025, RTE (http://www.rte-france.com)
  * See AUTHORS.txt
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -42,7 +42,8 @@ public CurrentUserWithPerimetersController(UserRepository userRepository, GroupR
                     PerimeterRepository perimeterRepository, EntityRepository entityRepository,
                     UserSettingsRepository userSettingsRepository, UserActionLogRepository userActionLogRepository,
                     EventBus eventBus,
-                    @Value("${operatorfabric.userActionLogActivated:true}") boolean userActionLogActivated) {
+                    @Value("${operatorfabric.userActionLogActivated:true}") boolean userActionLogActivated,
+                    @Value("${operatorfabric.users.getEmailFromUserInsteadOfSettings:false}") boolean getEmailFromUserInsteadOfSettings) {
 
             NotificationService notificationService = new NotificationService(userRepository, eventBus);
             UsersService usersService = new UsersService(userRepository, groupRepository, entityRepository,
@@ -52,7 +53,8 @@ public CurrentUserWithPerimetersController(UserRepository userRepository, GroupR
                             notificationService, userActionLogService, userActionLogActivated);
             this.currentUserWithPerimetersService = new CurrentUserWithPerimetersService(usersService,
                             userSettingsService,
-                            entityRepository);
+                            entityRepository,
+                            getEmailFromUserInsteadOfSettings);
     }
 
     @GetMapping(produces = { "application/json" })
diff --git a/services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java b/services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java
index b9036405e2..2a1d613f0e 100644
--- a/services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java
+++ b/services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java
@@ -1,4 +1,4 @@
-/* Copyright (c) 2023-2024, RTE (http://www.rte-france.com)
+/* Copyright (c) 2023-2025, RTE (http://www.rte-france.com)
  * See AUTHORS.txt
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -42,7 +42,8 @@ public UserWithPerimetersController(UserRepository userRepository, GroupReposito
                     PerimeterRepository perimeterRepository, EntityRepository entityRepository,
                     UserSettingsRepository userSettingsRepository, UserActionLogRepository userActionLogRepository,
                     EventBus eventBus,
-                    @Value("${operatorfabric.userActionLogActivated:true}") boolean userActionLogActivated) {
+                    @Value("${operatorfabric.userActionLogActivated:true}") boolean userActionLogActivated,
+                    @Value("${operatorfabric.users.getEmailFromUserInsteadOfSettings:false}") boolean getEmailFromUserInsteadOfSettings) {
 
             NotificationService notificationService = new NotificationService(userRepository, eventBus);
             UsersService usersService = new UsersService(userRepository, groupRepository, entityRepository,
@@ -52,7 +53,8 @@ public UserWithPerimetersController(UserRepository userRepository, GroupReposito
                             notificationService, userActionLogService, userActionLogActivated);
             this.currentUserWithPerimetersService = new CurrentUserWithPerimetersService(usersService,
                             userSettingsService,
-                            entityRepository);
+                            entityRepository,
+                            getEmailFromUserInsteadOfSettings);
     }
 
     @GetMapping(value = "/{login}", produces = { "application/json" })
diff --git a/services/users/src/main/java/org/opfab/users/model/User.java b/services/users/src/main/java/org/opfab/users/model/User.java
index bc42d0f9e8..3427e0e50c 100644
--- a/services/users/src/main/java/org/opfab/users/model/User.java
+++ b/services/users/src/main/java/org/opfab/users/model/User.java
@@ -1,4 +1,4 @@
-/* Copyright (c) 2018-2024, RTE (http://www.rte-france.com)
+/* Copyright (c) 2018-2025, RTE (http://www.rte-france.com)
  * See AUTHORS.txt
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -25,6 +25,7 @@ public class User {
     private String login;
     private String firstName;
     private String lastName;
+    private String email;
     private String comment;
     private Set<String> entities;
 
@@ -36,11 +37,12 @@ public User() {
 
     
 
-    public User(@NotNull String login, String firstName, String lastName, String comment, Set<String> entities,
+    public User(@NotNull String login, String firstName, String lastName, String email, String comment, Set<String> entities,
             Set<String> groupSet) {
         this.login = login;
         this.firstName = firstName;
         this.lastName = lastName;
+        this.email = email;
         this.comment = comment;
         this.entities = entities;
         this.groupSet = groupSet;
@@ -71,6 +73,14 @@ public void setLastName(String lastName) {
         this.lastName = lastName;
     }
 
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
     public String getComment() {
         return comment;
     }
@@ -134,6 +144,7 @@ public int hashCode() {
         result = prime * result + ((login == null) ? 0 : login.hashCode());
         result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
         result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
+        result = prime * result + ((email == null) ? 0 : email.hashCode());
         result = prime * result + ((comment == null) ? 0 : comment.hashCode());
         result = prime * result + ((entities == null) ? 0 : entities.hashCode());
         result = prime * result + ((groupSet == null) ? 0 : groupSet.hashCode());
@@ -165,6 +176,11 @@ public boolean equals(Object obj) {
                 return false;
         } else if (!lastName.equals(other.lastName))
             return false;
+        if (email == null) {
+            if (other.email != null)
+                return false;
+        } else if (!email.equals(other.email))
+            return false;
         if (comment == null) {
             if (other.comment != null)
                 return false;
diff --git a/services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java b/services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java
index bb3af901b9..bd792f7a65 100644
--- a/services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java
+++ b/services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java
@@ -21,11 +21,14 @@ public class CurrentUserWithPerimetersService {
     private UserSettingsService userSettingsService;
     private EntityRepository entityRepository;
 
+    private boolean getEmailFromUserInsteadOfSettings;
+
     public CurrentUserWithPerimetersService(UsersService usersService, UserSettingsService userSettingsService,
-            EntityRepository entityRepository) {
+            EntityRepository entityRepository, boolean getEmailFromUserInsteadOfSettings) {
         this.usersService = usersService;
         this.userSettingsService = userSettingsService;
         this.entityRepository = entityRepository;
+        this.getEmailFromUserInsteadOfSettings = getEmailFromUserInsteadOfSettings;
     }
 
     public CurrentUserWithPerimeters fetchUserWithPerimeters(String login) {
@@ -88,7 +91,11 @@ protected void handleUserSettings(User userData, CurrentUserWithPerimeters userW
                     .setDisableCardContentInEmails(operationResult.getResult().getDisableCardContentInEmails());
             userWithPerimeterData.setSendDailyEmail(operationResult.getResult().getSendDailyEmail());
             userWithPerimeterData.setSendWeeklyEmail(operationResult.getResult().getSendWeeklyEmail());
-            userWithPerimeterData.setEmail(operationResult.getResult().getEmail());
+            if (this.getEmailFromUserInsteadOfSettings) {
+                userWithPerimeterData.setEmail(userData.getEmail() == null ? "" : userData.getEmail());
+            } else {
+                userWithPerimeterData.setEmail(operationResult.getResult().getEmail());
+            }
             userWithPerimeterData.setTimezoneForEmails(operationResult.getResult().getTimezoneForEmails());
         }
     }
diff --git a/services/users/src/main/modeling/swagger.yaml b/services/users/src/main/modeling/swagger.yaml
index 71e83690ec..ddce8307da 100755
--- a/services/users/src/main/modeling/swagger.yaml
+++ b/services/users/src/main/modeling/swagger.yaml
@@ -34,6 +34,8 @@ definitions:
         type: string
       lastName:
         type: string
+      email:
+        type: string
       comment:
         type: string
       groups:
diff --git a/services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java b/services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java
index 0eb0c333f7..4412f232d1 100644
--- a/services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java
+++ b/services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java
@@ -174,7 +174,7 @@ void shouldContainsProcessNotNotified() {
                 initUserSettings();
 
                 CurrentUserWithPerimetersService currentUserWithPerimetersService = new CurrentUserWithPerimetersService(
-                                usersServiceStub, userSettingsService, entityRepositoryStub);
+                                usersServiceStub, userSettingsService, entityRepositoryStub, false);
                 CurrentUserWithPerimeters currentUser = currentUserWithPerimetersService
                                 .fetchCurrentUserWithPerimeters(user);
                 assertThat(currentUser.getProcessesStatesNotNotified().get("myprocess")).contains("mystate");
@@ -189,7 +189,7 @@ void shouldContainsUserEntities() {
                 user.addEntity(ENTITY_2);
 
                 CurrentUserWithPerimetersService currentUserWithPerimetersService = new CurrentUserWithPerimetersService(
-                                usersServiceStub, userSettingsService, entityRepositoryStub);
+                                usersServiceStub, userSettingsService, entityRepositoryStub, false);
                 CurrentUserWithPerimeters currentUser = currentUserWithPerimetersService
                                 .fetchCurrentUserWithPerimeters(user);
                 assertThat(currentUser.getUserData().getEntities()).contains(ENTITY_1, ENTITY_2);
@@ -205,7 +205,7 @@ void shouldNotContainsUserEntitiesNotConnected() {
                 user.addEntity(ENTITY_NOT_CONNECTED);
 
                 CurrentUserWithPerimetersService currentUserWithPerimetersService = new CurrentUserWithPerimetersService(
-                                usersServiceStub, userSettingsService, entityRepositoryStub);
+                                usersServiceStub, userSettingsService, entityRepositoryStub, false);
                 CurrentUserWithPerimeters currentUser = currentUserWithPerimetersService
                                 .fetchCurrentUserWithPerimeters(user);
                 assertThat(currentUser.getUserData().getEntities()).contains(ENTITY_1, ENTITY_2);
@@ -219,7 +219,7 @@ void shouldContainsUserEntitiesParents() {
                 user.addEntity(ENTITY_1);
 
                 CurrentUserWithPerimetersService currentUserWithPerimetersService = new CurrentUserWithPerimetersService(
-                                usersServiceStub, userSettingsService, entityRepositoryStub);
+                                usersServiceStub, userSettingsService, entityRepositoryStub, false);
                 CurrentUserWithPerimeters currentUser = currentUserWithPerimetersService
                                 .fetchCurrentUserWithPerimeters(user);
                 assertThat(currentUser.getUserData().getEntities()).contains(GRAND_CHILD_ENTITY, CHILD_ENTITY,
@@ -236,7 +236,7 @@ void shouldNotContainsUserEntitiesChilds() {
                 user.addEntity(ENTITY_1);
 
                 CurrentUserWithPerimetersService currentUserWithPerimetersService = new CurrentUserWithPerimetersService(
-                                usersServiceStub, userSettingsService, entityRepositoryStub);
+                                usersServiceStub, userSettingsService, entityRepositoryStub, false);
                 CurrentUserWithPerimeters currentUser = currentUserWithPerimetersService
                                 .fetchCurrentUserWithPerimeters(user);
                 assertThat(currentUser.getUserData().getEntities()).contains(ROOT_ENTITY, ENTITY_1);
@@ -251,7 +251,7 @@ void GIVEN_User_With_One_Group_Without_Permissions_WHEN_Fetching_CurrentUserWith
                 user.addGroup(GROUP_3);
 
                 CurrentUserWithPerimetersService currentUserWithPerimetersService = new CurrentUserWithPerimetersService(
-                                usersServiceStub, userSettingsService, entityRepositoryStub);
+                                usersServiceStub, userSettingsService, entityRepositoryStub, false);
                 CurrentUserWithPerimeters currentUser = currentUserWithPerimetersService
                                 .fetchCurrentUserWithPerimeters(user);
                 assertThat(currentUser.getPermissions()).isEmpty();
@@ -267,7 +267,7 @@ void GIVEN_User_With_Many_Groups_WHEN_Fetching_CurrentUserWithPerimeters_THEN_Re
                 user.addGroup(GROUP_4);
 
                 CurrentUserWithPerimetersService currentUserWithPerimetersService = new CurrentUserWithPerimetersService(
-                                usersServiceStub, userSettingsService, entityRepositoryStub);
+                                usersServiceStub, userSettingsService, entityRepositoryStub, false);
                 CurrentUserWithPerimeters currentUser = currentUserWithPerimetersService
                                 .fetchCurrentUserWithPerimeters(user);
                 assertThat(currentUser.getPermissions()).containsExactlyInAnyOrder(PermissionEnum.READONLY,
@@ -279,7 +279,7 @@ void GIVEN_User_With_Many_Groups_WHEN_Fetching_CurrentUserWithPerimeters_THEN_Re
         void GIVEN_User_Login_WHEN_FetchingUserWithPerimeters_CurrentUserWithPerimetersShouldContainsUserEntitiesParents() {
                 String login = "testLogin";
                 CurrentUserWithPerimetersService currentUserWithPerimetersService = new CurrentUserWithPerimetersService(
-                                usersServiceStub, userSettingsService, entityRepositoryStub);
+                                usersServiceStub, userSettingsService, entityRepositoryStub, false);
                 CurrentUserWithPerimeters currentUser = currentUserWithPerimetersService
                                 .fetchUserWithPerimeters(login);
                 assertThat(currentUser.getUserData().getEntities()).contains(GRAND_CHILD_ENTITY, CHILD_ENTITY,
diff --git a/services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java b/services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java
index 355f681db6..b762a8d70f 100644
--- a/services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java
+++ b/services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java
@@ -54,9 +54,9 @@ void clear() {
 
         Set<String> entitiesForUser1 = new HashSet<>(Arrays.asList("entity1", "entity2"));
         Set<String> entitiesForUser2 = new HashSet<>(Arrays.asList("entity2"));
-        userRepositoryStub.insert(new User("user1", "test", null, null, entitiesForUser1, null));
-        userRepositoryStub.insert(new User("user2", "test", null, null, entitiesForUser2, null));
-        userRepositoryStub.insert(new User("user3", "test", null, null, null, null));
+        userRepositoryStub.insert(new User("user1", "test", null, null, null, entitiesForUser1, null));
+        userRepositoryStub.insert(new User("user2", "test", null, null, null, entitiesForUser2, null));
+        userRepositoryStub.insert(new User("user3", "test", null, null, null, null, null));
 
     }
 
diff --git a/services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java b/services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java
index 25e6b74753..ce63719ca3 100644
--- a/services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java
+++ b/services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java
@@ -56,13 +56,13 @@ void clear() {
         groupRepositoryStub.save(groupAdmin);
         Set<String> groupForUser1 = new HashSet<>();
         groupForUser1.add("group1");
-        userRepositoryStub.insert(new User("user1", "test", null, null,null, groupForUser1));
+        userRepositoryStub.insert(new User("user1", "test", null, null, null,null, groupForUser1));
         Set<String> groupForUser2 = new HashSet<>();
         groupForUser2.add("group1");
-        userRepositoryStub.insert(new User("user2", "test", null, null,null, groupForUser2));
+        userRepositoryStub.insert(new User("user2", "test", null, null, null,null, groupForUser2));
         Set<String> groupForAdmin = new HashSet<>();
         groupForAdmin.add("ADMIN");
-        userRepositoryStub.insert(new User("admin", "admin", null, null,null, groupForAdmin));
+        userRepositoryStub.insert(new User("admin", "admin", null, null, null,null, groupForAdmin));
 
         groupsService = new GroupsService(groupRepositoryStub, userRepositoryStub,
                 perimeterRepositoryStub, notificationService);
diff --git a/services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java b/services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java
index 7b04465c9c..4fbd6f4831 100644
--- a/services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java
+++ b/services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java
@@ -1,4 +1,4 @@
-/* Copyright (c) 2022-2024, RTE (http://www.rte-france.com)
+/* Copyright (c) 2022-2025, RTE (http://www.rte-france.com)
  * See AUTHORS.txt
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -103,10 +103,10 @@ private void initUserRepository() {
         userRepositoryStub.deleteAll();
         Set<String> groupForUser1 = new HashSet<>();
         groupForUser1.add("group1");
-        userRepositoryStub.insert(new User("user1", "test", null, null, null, groupForUser1));
+        userRepositoryStub.insert(new User("user1", "test", null, null, null, null, groupForUser1));
         Set<String> groupForUser2 = new HashSet<>();
         groupForUser2.add("group1");
-        userRepositoryStub.insert(new User("user2", "test", null, null, null, groupForUser2));
+        userRepositoryStub.insert(new User("user2", "test", null, null, null, null, groupForUser2));
 
     }
  
diff --git a/services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java b/services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java
index 933dafb12b..6a5d5433ab 100644
--- a/services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java
+++ b/services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java
@@ -1,4 +1,4 @@
-/* Copyright (c) 2023-2024, RTE (http://www.rte-france.com)
+/* Copyright (c) 2023-2025, RTE (http://www.rte-france.com)
  * See AUTHORS.txt
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -126,6 +126,7 @@ private void initUserRepository() {
         u1.setLogin("user1");
         u1.setFirstName("user1FirstName");
         u1.setLastName("user1LastName");
+        u1.setEmail("user1FirstName.user1LastName@test.fr");
         u1.setComment("comment");
         u1.addGroup("group1");
         u1.addGroup("group2");
@@ -173,6 +174,7 @@ void GIVEN_Existing_User_In_Repository_WHEN_Fetch_User_THEN_Success_And_Return_U
             assertThat(user.getResult().getLogin()).isEqualTo("user1");
             assertThat(user.getResult().getFirstName()).isEqualTo("user1FirstName");
             assertThat(user.getResult().getLastName()).isEqualTo("user1LastName");
+            assertThat(user.getResult().getEmail()).isEqualTo("user1FirstName.user1LastName@test.fr");
 
         }
 
@@ -188,6 +190,7 @@ void GIVEN_A_Valid_User_WHEN_Create_User_THEN_Return_Created_User() {
             user.setLogin("newuser");
             user.setFirstName("firstName");
             user.setLastName("user1LastName");
+            user.setEmail("firstName.user1LastName@test.fr");
             user.addGroup("group1");
             user.addGroup("group2");
 
@@ -198,6 +201,7 @@ void GIVEN_A_Valid_User_WHEN_Create_User_THEN_Return_Created_User() {
             assertThat(result.getResult().getEntity().getFirstName()).isEqualTo("firstName");
             assertThat(userRepositoryStub.findById("newuser").get().getLogin()).isEqualTo("newuser");
             assertThat(userRepositoryStub.findById("newuser").get().getFirstName()).isEqualTo("firstName");
+            assertThat(userRepositoryStub.findById("newuser").get().getEmail()).isEqualTo("firstName.user1LastName@test.fr");
         }
 
         @Test
diff --git a/src/docs/asciidoc/deployment/configuration/configuration.adoc b/src/docs/asciidoc/deployment/configuration/configuration.adoc
index 3fada9e86c..6f347aa01c 100644
--- a/src/docs/asciidoc/deployment/configuration/configuration.adoc
+++ b/src/docs/asciidoc/deployment/configuration/configuration.adoc
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2024 RTE (http://www.rte-france.com)
+// Copyright (c) 2018-2025 RTE (http://www.rte-france.com)
 // See AUTHORS.txt
 // This document is subject to the terms of the Creative Commons Attribution 4.0 International license.
 // If a copy of the license was not distributed with this
@@ -116,6 +116,8 @@ The user service has these specific properties :
 |operatorfabric.users.default.groups|null| Array of group objects to create upon startup if they don't exist
 |operatorfabric.users.default.entities|null| Array of entity objects to create upon startup if they don't exist
 |operatorfabric.users.daysBeforeLogExpiration|61| Duration beyond which user action logs get automatically deleted
+|operatorfabric.users.getEmailFromUserInsteadOfSettings|false| If set to true, the email address used to notify the user
+is fetched from the token, otherwise the user must fill the field on the settings screen
 
 |===
 
diff --git a/src/docs/asciidoc/deployment/configuration/web-ui_configuration.adoc b/src/docs/asciidoc/deployment/configuration/web-ui_configuration.adoc
index 557f83bab7..f1cd923d17 100644
--- a/src/docs/asciidoc/deployment/configuration/web-ui_configuration.adoc
+++ b/src/docs/asciidoc/deployment/configuration/web-ui_configuration.adoc
@@ -198,6 +198,7 @@ a|authentication mode, available options:
 
 |settings.dateFormat|Value from the browser configuration|no|Format for date rendering (example : dd/MM/yyyy )
 |settings.dateTimeFormat|Value from the browser configuration|no|Format for date and time rendering (example : HH:mm dd/MM/yyyy )
+|settings.getEmailFromUserInsteadOfSettings|false|no|If set to true, the email address used to notify the user is fetched from the token, otherwise the user must fill the field on the settings screen
 |settings.locale|en|no|Default user locale (use en if not set)
 |settings.playSoundForAction|false|no|If set to true, a sound is played when Action cards are added or updated in the feed
 |settings.playSoundForAlarm|false|no|If set to true, a sound is played when Alarm cards are added or updated in the feed
diff --git a/src/docs/asciidoc/reference_doc/mail_notification.adoc b/src/docs/asciidoc/reference_doc/mail_notification.adoc
index 47ba95e75f..9643431ab1 100644
--- a/src/docs/asciidoc/reference_doc/mail_notification.adoc
+++ b/src/docs/asciidoc/reference_doc/mail_notification.adoc
@@ -1,4 +1,4 @@
-// Copyright (c) 2023-2024 RTE (http://www.rte-france.com)
+// Copyright (c) 2023-2025 RTE (http://www.rte-france.com)
 // See AUTHORS.txt
 // This document is subject to the terms of the Creative Commons Attribution 4.0 International license.
 // If a copy of the license was not distributed with this
@@ -12,7 +12,11 @@ OpFab can send card notifications via email. These notifications are sent when a
 
 == Configuring Email Notifications
 
-Users can enable email notifications in their user settings. They must provide an email address for receiving notifications and select the processes/states they want to be notified about.
+Users can enable email notifications in their user settings. If the parameter
+`operatorfabric.users.getEmailFromUserInsteadOfSettingsThey` is set to true, the email address is fetched from the token.
+The email address is displayed in the settings screen but the field is readonly.
+If the parameter is absent or set to false, the user must provide an email address (in the settings screen) for receiving
+notifications and select the processes/states they want to be notified about.
 Users can specify the desired timezone for displaying dates and times in emails. To do this, they must fill in
 the "timezone for mails" field in the user settings screen.
 
diff --git a/tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java b/tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java
index 1d83a84aa9..5f92cc6f91 100644
--- a/tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java
+++ b/tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java
@@ -23,6 +23,7 @@ public class JwtProperties {
     private String givenNameClaim = "given_name";
     private String familyNameClaim = "family_name";
     private String nameClaim = "name";
+    private String emailClaim = "email";
 
     private String entitiesIdClaim = "entitiesId";
     private boolean gettingEntitiesFromToken = false;
@@ -43,6 +44,10 @@ public String getNameClaim() {
         return nameClaim;
     }
 
+    public String getEmailClaim() {
+        return emailClaim;
+    }
+
     public String getEntitiesIdClaim() {
         return entitiesIdClaim;
     }
@@ -77,6 +82,10 @@ public void setNameClaim(String nameClaim) {
         this.nameClaim = nameClaim;
     }
 
+    public void setEmailClaim(String emailClaim) {
+        this.emailClaim = emailClaim;
+    }
+
     public void setEntitiesIdClaim(String entitiesIdClaim) {
         this.entitiesIdClaim = entitiesIdClaim;
     }
diff --git a/ui/main/src/app/components/settings/SettingsComponent.html b/ui/main/src/app/components/settings/SettingsComponent.html
index 4f948b7b51..794b258585 100644
--- a/ui/main/src/app/components/settings/SettingsComponent.html
+++ b/ui/main/src/app/components/settings/SettingsComponent.html
@@ -127,7 +127,19 @@
         <div class="opfab-settings-item" *ngIf="settingsView.isSettingVisible('sendCardsByEmail')">
             <div class="opfab-input">
                 <label for="opfab-setting-email" translate>settings.email</label>
-                <input id="opfab-setting-input-email" type="email" formControlName="email" autocomplete="off" />
+                <input
+                    *ngIf="settingsView.isEmailFromUserInsteadOfSettings()"
+                    id="opfab-setting-input-email"
+                    type="email"
+                    formControlName="email"
+                    autocomplete="off"
+                    readonly />
+                <input
+                    *ngIf="!settingsView.isEmailFromUserInsteadOfSettings()"
+                    id="opfab-setting-input-email"
+                    type="email"
+                    formControlName="email"
+                    autocomplete="off" />
             </div>
             <div *ngIf="email.invalid" class="opfab-input-invalid" style="margin-top: 0px">
                 <div *ngIf="email.errors.email" translate>settings.input.errors.invalidEmail</div>
diff --git a/ui/main/src/app/components/settings/SettingsComponent.ts b/ui/main/src/app/components/settings/SettingsComponent.ts
index accb58b861..fdf254f496 100644
--- a/ui/main/src/app/components/settings/SettingsComponent.ts
+++ b/ui/main/src/app/components/settings/SettingsComponent.ts
@@ -94,9 +94,11 @@ export class SettingsComponent implements OnInit, OnDestroy {
 
         const formGroupConfig = {};
         settings.forEach((setting) => {
-            if (setting === 'email')
+            if (setting === 'email') {
                 formGroupConfig[setting] = new FormControl(this.settingsView.getSetting(setting), Validators.email);
-            else formGroupConfig[setting] = new FormControl(this.settingsView.getSetting(setting));
+            } else {
+                formGroupConfig[setting] = new FormControl(this.settingsView.getSetting(setting));
+            }
         });
         this.settingsForm = new FormGroup(formGroupConfig, {updateOn: 'change'});
         this.initLocaleMultiselect();
diff --git a/ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts b/ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts
index 091d651eee..54badcbe9d 100644
--- a/ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts
+++ b/ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts
@@ -54,7 +54,8 @@ export class NotificationConfigurationView {
             (ConfigService.getConfigValue('settings.sendDailyEmail', false) ||
                 ConfigService.getConfigValue('settings.sendWeeklyEmail', false) ||
                 ConfigService.getConfigValue('settings.sendCardsByEmail', false)) &&
-            ConfigService.getConfigValue('settings.email')?.length > 0;
+            (ConfigService.getConfigValue('settings.email')?.length > 0 ||
+                this.isEmailFromUserInsteadOfSettingsAndFieldIsNotEmpty());
 
         this.notificationConfigurationPage.isThereProcessStatesToDisplay =
             this.notificationConfigurationPage.processGroups.length > 0 ||
@@ -63,6 +64,13 @@ export class NotificationConfigurationView {
         this.lastProcessesStatesNotifiedByEmailSaved = this.getCheckedStatesForEmailNotificationPerProcesses();
     }
 
+    private isEmailFromUserInsteadOfSettingsAndFieldIsNotEmpty(): boolean {
+        return (
+            ConfigService.getConfigValue('settings.getEmailFromUserInsteadOfSettings', false) &&
+            UsersService.getCurrentUserWithPerimeters().email?.length > 0
+        );
+    }
+
     private getProcessList(): ProcessForNotification[] {
         const processesForNotification: ProcessForNotification[] = [];
         ProcessesService.getAllProcesses().forEach((process) => {
diff --git a/ui/main/src/app/views/settings/SettingsView.ts b/ui/main/src/app/views/settings/SettingsView.ts
index 2a551ae71b..3136998d39 100644
--- a/ui/main/src/app/views/settings/SettingsView.ts
+++ b/ui/main/src/app/views/settings/SettingsView.ts
@@ -24,6 +24,10 @@ export class SettingsView {
         return !ConfigService.getConfigValue('settingsScreen.hiddenSettings', []).includes(setting);
     }
 
+    public isEmailFromUserInsteadOfSettings(): boolean {
+        return ConfigService.getConfigValue('settings.getEmailFromUserInsteadOfSettings', false);
+    }
+
     public getSetting(setting: string): string | boolean | number {
         switch (setting) {
             case 'replayInterval':
@@ -32,6 +36,12 @@ export class SettingsView {
             case 'timezoneForEmails':
                 return ConfigService.getConfigValue('settings.timezoneForEmails', 'Europe/Paris');
 
+            case 'email':
+                if (this.isEmailFromUserInsteadOfSettings()) {
+                    return UsersService.getCurrentUserWithPerimeters().email;
+                } else {
+                    return ConfigService.getConfigValue('settings.' + setting);
+                }
             default:
                 return ConfigService.getConfigValue('settings.' + setting);
         }


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>services/users/src/main/java/org/opfab/users/model/User.java (1)</summary><blockquote>

`40-49`: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Constructor signature change is a breaking change.**

The User constructor now requires an email parameter. This is a breaking change that will affect any code that directly instantiates User objects.


---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Find all User constructor usages to verify they've been updated
# Expected: All constructor calls should include the email parameter

ast-grep --pattern 'new User($_)'
```

Length of output: 35

---

🏁 Script executed:

```shell
#!/bin/bash
set -e
# Search across all Java files for direct User constructor calls
rg -n "new User\\(" -g "*.java" .
```

Length of output: 9056

---



</details>

**Fix broken User constructor usages**

The `User` constructor now requires an `email` parameter, so all instantiations using the old signature or the no-arg constructor will fail to compile. Update each call site to provide a valid email (or add an overloaded constructor for backward compatibility).

Key locations to address:
- tools/spring/spring-test-utilities/src/main/java/org/opfab/springtools/configuration/test/OpFabUserDetails.java:36  
- services/users/src/main/java/org/opfab/users/services/UserSettingsService.java:141  
- Unit tests in services/users/src/test/java/org/opfab/users/services/ (e.g., `EntitiesServiceShould.java`, `PerimetersServiceShould.java`, `UsersServiceShould.java`, etc.)  
- Integration/tests in services/cards-consultation and services/cards-publication (multiple `new User()` calls)

You can run:
```bash
rg -n "new User\(" -g "*.java"
```
to find all remaining call sites and update them accordingly.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>services/users/src/main/modeling/swagger.yaml (1)</summary><blockquote>

`37-38`: **Include `email` in Swagger `User` schema**  
The `User` definition now has an optional `email` property. To improve clarity, you may want to update the `example` section under `User` to include a sample `email`.

</blockquote></details>
<details>
<summary>ui/main/src/app/components/settings/SettingsComponent.html (1)</summary><blockquote>

`130-142`: **Consider refactoring to avoid duplicate element IDs.**

The conditional email input logic is correct, but having two input elements with the same ID (`opfab-setting-input-email`) could cause accessibility and DOM validation issues, even though only one is rendered at a time.



Consider using a single input element with dynamic readonly attribute:

```diff
-                <input
-                    *ngIf="settingsView.isEmailFromUserInsteadOfSettings()"
-                    id="opfab-setting-input-email"
-                    type="email"
-                    formControlName="email"
-                    autocomplete="off"
-                    readonly />
-                <input
-                    *ngIf="!settingsView.isEmailFromUserInsteadOfSettings()"
-                    id="opfab-setting-input-email"
-                    type="email"
-                    formControlName="email"
-                    autocomplete="off" />
+                <input
+                    id="opfab-setting-input-email"
+                    type="email"
+                    formControlName="email"
+                    autocomplete="off"
+                    [readonly]="settingsView.isEmailFromUserInsteadOfSettings()" />
```

</blockquote></details>
<details>
<summary>config/docker/users.yml (1)</summary><blockquote>

`8-170`: **Fix YAML formatting issues for consistency.**

Multiple YAML formatting issues were detected by static analysis, including spacing after commas, spacing before colons, and indentation problems.


Apply these formatting fixes to improve consistency:

```diff
-        groups: ["ENTITY1_FR","ENTITY2_FR"]
+        groups: ["ENTITY1_FR", "ENTITY2_FR"]
-        firstname : John
-        lastname : Doe
+        firstname: John
+        lastname: Doe
-        groups: [ "RTE","ADMIN","Maintainer","Dispatcher"]
+        groups: ["RTE", "ADMIN", "Maintainer", "Dispatcher"]
-     - id: ENTITY1_FR
+      - id: ENTITY1_FR
-       parents : ["ENTITY_FR"]
+       parents: ["ENTITY_FR"]
```

Consider running a YAML formatter to address all spacing and indentation issues consistently.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[warning] 11-11: too few spaces after comma

(commas)

---

[warning] 13-13: too many spaces before colon

(colons)

---

[warning] 14-14: too many spaces before colon

(colons)

---

[warning] 15-15: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 24-24: too few spaces after comma

(commas)

---

[warning] 25-25: too many spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 62-62: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 95-95: wrong indentation: expected 6 but found 5

(indentation)

---

[warning] 98-98: too many spaces before colon

(colons)

---

[warning] 104-104: too many spaces before colon

(colons)

---

[warning] 109-109: too many spaces before colon

(colons)

---

[warning] 114-114: too many spaces before colon

(colons)

---

[warning] 127-127: too many spaces before colon

(colons)

---

[warning] 132-132: too many spaces before colon

(colons)

---

[warning] 137-137: too many spaces before colon

(colons)

---

[warning] 142-142: too many spaces before colon

(colons)

---

[warning] 151-151: too many spaces before colon

(colons)

---

[warning] 156-156: too many spaces before colon

(colons)

---

[error] 170-170: no new line character at the end of file

(new-line-at-end-of-file)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1e9b221da35f32dd96ae95ecc3eacd691fc09bd2 and aac2733d1361c84082426b06cde9b80923046773.

</details>

<details>
<summary>📒 Files selected for processing (19)</summary>

* `config/docker/ui-config/web-ui.json` (1 hunks)
* `config/docker/users.yml` (1 hunks)
* `config/keycloak/export/dev-users-0.json` (1 hunks)
* `services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java` (2 hunks)
* `services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java` (3 hunks)
* `services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java` (3 hunks)
* `services/users/src/main/java/org/opfab/users/model/User.java` (6 hunks)
* `services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java` (2 hunks)
* `services/users/src/main/modeling/swagger.yaml` (1 hunks)
* `services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java` (8 hunks)
* `services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java` (1 hunks)
* `services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java` (1 hunks)
* `services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java` (2 hunks)
* `services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java` (5 hunks)
* `tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java` (3 hunks)
* `ui/main/src/app/components/settings/SettingsComponent.html` (1 hunks)
* `ui/main/src/app/components/settings/SettingsComponent.ts` (1 hunks)
* `ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts` (2 hunks)
* `ui/main/src/app/views/settings/SettingsView.ts` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

<details>
<summary>config/docker/users.yml</summary>

[warning] 11-11: too few spaces after comma

(commas)

---

[warning] 13-13: too many spaces before colon

(colons)

---

[warning] 14-14: too many spaces before colon

(colons)

---

[warning] 15-15: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 24-24: too few spaces after comma

(commas)

---

[warning] 25-25: too many spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 62-62: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 95-95: wrong indentation: expected 6 but found 5

(indentation)

---

[warning] 98-98: too many spaces before colon

(colons)

---

[warning] 104-104: too many spaces before colon

(colons)

---

[warning] 109-109: too many spaces before colon

(colons)

---

[warning] 114-114: too many spaces before colon

(colons)

---

[warning] 127-127: too many spaces before colon

(colons)

---

[warning] 132-132: too many spaces before colon

(colons)

---

[warning] 137-137: too many spaces before colon

(colons)

---

[warning] 142-142: too many spaces before colon

(colons)

---

[warning] 151-151: too many spaces before colon

(colons)

---

[warning] 156-156: too many spaces before colon

(colons)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: build
* GitHub Check: build

</details>

<details>
<summary>🔇 Additional comments (27)</summary><blockquote>

<details>
<summary>tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java (3)</summary>

`26-26`: **Add `emailClaim` property for JWT email extraction**  
The new `emailClaim` field aligns with other optional claims and defaults to `"email"`. This follows established patterns and will map from `email-claim` in configuration.

---

`47-49`: **Expose `getEmailClaim()` getter**  
The getter method is correctly implemented. It will allow consumers to read the configured claim key for email from `JwtProperties`.

---

`85-87`: **Implement `setEmailClaim()` setter**  
The setter ensures Spring can inject `email-claim` from kebab-case configuration. This matches the existing pattern for other claims.

</details>
<details>
<summary>config/keycloak/export/dev-users-0.json (1)</summary>

`220-220`: **Add `email` field to Keycloak export**  
The new `"email": "operator2_fr@test.com"` entry ensures the user object has the email property for later retrieval. Consider whether you want to set `"emailVerified": true` here if assuming the exported email is already verified.

</details>
<details>
<summary>config/docker/ui-config/web-ui.json (1)</summary>

`131-133`: **Introduce `getEmailFromUserInsteadOfSettings` UI flag**  
The new `"getEmailFromUserInsteadOfSettings": true` setting enables the frontend to fetch the email from the user entity. This aligns with the backend flag and feature.

</details>
<details>
<summary>services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java (2)</summary>

`1-1`: **Bump copyright year to 2025**  
The header has been updated correctly from 2024 to 2025, keeping the file header in sync with the project.

---

`106-109`: **Update test user instantiation to match new `User` constructor**  
The inserted `null` argument aligns with the additional `email` parameter on the `User` constructor. This change is necessary to reflect the updated signature.

</details>
<details>
<summary>services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java (1)</summary>

`59-65`: **LGTM! Constructor calls updated correctly for new User model.**

The User constructor calls have been properly updated to include the new `email` parameter, maintaining consistency with the updated User model signature. The `null` values for email are appropriate for test data setup.

</details>
<details>
<summary>ui/main/src/app/components/settings/SettingsComponent.ts (1)</summary>

`97-101`: **Good code style improvement with explicit braces.**

Adding explicit braces around the if-else blocks improves code readability and follows better coding practices. The functionality remains unchanged while making the conditional logic more explicit.

</details>
<details>
<summary>services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java (1)</summary>

`57-59`: **LGTM! User constructor calls properly updated.**

The User constructor calls have been correctly updated to include the new `email` parameter as `null`, maintaining proper parameter positioning with entities as the sixth parameter. This ensures test compatibility with the updated User model.

</details>
<details>
<summary>services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java (2)</summary>

`24-24`: **LGTM! Clean addition of configuration flag.**

The new boolean field follows good naming conventions and is properly initialized via constructor injection, enabling dependency injection of the configuration value.



Also applies to: 31-31

---

`94-98`: **LGTM! Proper email source selection with null safety.**

The conditional logic correctly implements the email source selection:
- When flag is `true`: Uses user entity email with proper null safety (empty string fallback)
- When flag is `false`: Uses settings email as before

The null safety check `userData.getEmail() == null ? "" : userData.getEmail()` prevents potential NullPointerExceptions.

</details>
<details>
<summary>ui/main/src/app/views/settings/SettingsView.ts (2)</summary>

`27-29`: **LGTM! Consistent configuration flag retrieval.**

The method correctly retrieves the configuration flag with appropriate default value (`false`), maintaining consistency with the backend implementation.

---

`39-44`: **LGTM! Proper conditional email source selection.**

The special handling for the 'email' setting correctly implements the same logic as the backend:
- Uses current user's email when flag is enabled
- Falls back to configuration settings otherwise

This maintains consistency between frontend and backend email source selection.

</details>
<details>
<summary>services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java (1)</summary>

`176-177`: **LGTM! Consistent test updates for new constructor parameter.**

All test instantiations correctly include the new `getEmailFromUserInsteadOfSettings` parameter with `false` value. This maintains existing test behavior while accommodating the service constructor changes.



Also applies to: 191-192, 207-208, 221-222, 238-239, 253-254, 269-270, 281-282

</details>
<details>
<summary>services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java (2)</summary>

`129-129`: **LGTM! Proper email field initialization for test data.**

The email follows a reasonable format pattern (`firstName.lastName@domain`) for test data, providing realistic test scenarios for the new email field functionality.

---

`177-177`: **LGTM! Comprehensive test coverage for email field.**

The assertions properly validate:
- Email retrieval during user fetch operations
- Email persistence during user creation
- Email storage verification in the repository

This ensures the new email field is fully tested across the service layer.



Also applies to: 193-193, 204-204

</details>
<details>
<summary>services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java (2)</summary>

`100-100`: **LGTM! Email extraction properly integrated.**

The email extraction from JWT claims is correctly implemented and positioned after other token processing logic.

---

`127-132`: <details>
<summary>✅ Verification successful</summary>

**Verify null handling for email claims.**

The implementation correctly extracts the email claim for virtual user creation. However, ensure that the system gracefully handles cases where the email claim is missing or null in the JWT token.


---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Check if extractClaimAsStringOrNull method properly handles null email claims
# Expected: Method should return null for missing claims without throwing exceptions

rg -A 10 "extractClaimAsStringOrNull.*email" --type java
```

Length of output: 58

---

🏁 Script executed:

```shell
#!/bin/bash
# Locate the extractClaimAsStringOrNull method definition and inspect its implementation
rg -A5 "extractClaimAsStringOrNull" --type java
```

Length of output: 3069

---



</details>

**Null handling confirmed for email claim**

The helper `extractClaimAsStringOrNull` first checks for a `null` claim key and returns `null` in that case. In Spring Security’s `Jwt` implementation, a missing claim also yields `null` from `getClaimAsString`, so no exception will be thrown and `email` will safely be `null` when the JWT lacks an email.

No further changes required here.

</details>
<details>
<summary>services/users/src/main/java/org/opfab/users/model/User.java (3)</summary>

`28-28`: **LGTM! Email field properly added.**

The email field is correctly declared as a private String member variable.

---

`76-82`: **LGTM! Email getter and setter properly implemented.**

The email getter and setter methods follow the standard Java bean conventions.

---

`147-147`: **LGTM! Email properly integrated in equals and hashCode.**

The email field is correctly included in both the hashCode calculation and equals comparison, maintaining the contract between these methods.



Also applies to: 179-183

</details>
<details>
<summary>services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java (2)</summary>

`45-46`: **LGTM! Configuration flag properly integrated.**

The new `getEmailFromUserInsteadOfSettings` configuration property is correctly injected with a sensible default value of `false`, ensuring backward compatibility.

---

`56-57`: **LGTM! Flag correctly passed to service constructor.**

The configuration flag is properly passed to the `CurrentUserWithPerimetersService` constructor, completing the integration of the email source selection feature.

</details>
<details>
<summary>ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts (1)</summary>

`57-58`: **LGTM: Email enablement logic correctly extended**

The modification properly extends the email enablement condition to support retrieving email from user profile. The logical OR operator allows email notifications when either the settings email is configured OR when the new flag is enabled with a valid user email.

</details>
<details>
<summary>services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java (2)</summary>

`46-46`: **LGTM: Proper Spring configuration injection**

The new constructor parameter correctly uses Spring's `@Value` annotation with a sensible default value of `false`, ensuring backward compatibility.

---

`56-57`: **LGTM: Parameter correctly passed to service**

The new configuration parameter is properly passed to the `CurrentUserWithPerimetersService` constructor, enabling the service layer to control email sourcing behavior.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-27T15:36:06Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>config/docker/users.yml (1)</summary><blockquote>

`5-8`: **Document configuration breaking change and update migration guides**  
The new `operatorfabric.users` parent key and nested `default` section with the `getEmailFromUserInsteadOfSettings` flag is a breaking change. Please ensure existing deployment scripts, Helm charts, and README examples are updated, and add clear migration documentation on how to transition from the old flat `users` structure to this new format.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>config/docker/users.yml (3)</summary><blockquote>

`5-6`: **Add documentation for the new email source flag**  
Consider updating your configuration reference (e.g., `docs/configuration.md`) to explain `getEmailFromUserInsteadOfSettings`, its default (`false`), and how it impacts whether emails are pulled from user profiles or settings.

---

`9-62`: **Normalize YAML list spacing for readability**  
YAMLlint flags too few or too many spaces after commas in list definitions. For clarity and to prevent parsing quirks, update entries like:  

```diff
- entities: ["ENTITY1_FR","ENTITY2_FR"]
+ entities: ["ENTITY1_FR", "ENTITY2_FR"]
```  

Apply this style consistently across all `groups` and `entities` lists.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[warning] 11-11: too few spaces after comma

(commas)

---

[warning] 13-13: too many spaces before colon

(colons)

---

[warning] 14-14: too many spaces before colon

(colons)

---

[warning] 15-15: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 24-24: too few spaces after comma

(commas)

---

[warning] 25-25: too many spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 62-62: too few spaces after comma

(commas)

</details>

</details>

---

`94-95`: **Correct indentation of entity list items**  
The list under `entities:` is mis-indented (expected 6 spaces, found 5). For example:  

```diff
-     - id: ENTITY1_FR
+      - id: ENTITY1_FR
```  

Adjust the indentation for all entity entries to match the YAML hierarchy.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[warning] 95-95: wrong indentation: expected 6 but found 5

(indentation)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between aac2733d1361c84082426b06cde9b80923046773 and 8f6063196907ed30bb3350062046a7af9c5f0e22.

</details>

<details>
<summary>📒 Files selected for processing (19)</summary>

* `config/docker/ui-config/web-ui.json` (1 hunks)
* `config/docker/users.yml` (1 hunks)
* `config/keycloak/export/dev-users-0.json` (1 hunks)
* `services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java` (2 hunks)
* `services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java` (3 hunks)
* `services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java` (3 hunks)
* `services/users/src/main/java/org/opfab/users/model/User.java` (6 hunks)
* `services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java` (2 hunks)
* `services/users/src/main/modeling/swagger.yaml` (1 hunks)
* `services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java` (8 hunks)
* `services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java` (1 hunks)
* `services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java` (1 hunks)
* `services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java` (2 hunks)
* `services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java` (5 hunks)
* `tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java` (3 hunks)
* `ui/main/src/app/components/settings/SettingsComponent.html` (1 hunks)
* `ui/main/src/app/components/settings/SettingsComponent.ts` (1 hunks)
* `ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts` (2 hunks)
* `ui/main/src/app/views/settings/SettingsView.ts` (2 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (17)</summary>

* config/keycloak/export/dev-users-0.json
* ui/main/src/app/components/settings/SettingsComponent.html
* config/docker/ui-config/web-ui.json
* services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java
* services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java
* ui/main/src/app/components/settings/SettingsComponent.ts
* ui/main/src/app/views/settings/SettingsView.ts
* ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts
* services/users/src/main/modeling/swagger.yaml
* services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java
* services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java
* services/users/src/main/java/org/opfab/users/model/User.java
* services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java
* services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java
* tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java
* services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java
* services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

<details>
<summary>config/docker/users.yml</summary>

[warning] 11-11: too few spaces after comma

(commas)

---

[warning] 13-13: too many spaces before colon

(colons)

---

[warning] 14-14: too many spaces before colon

(colons)

---

[warning] 15-15: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 24-24: too few spaces after comma

(commas)

---

[warning] 25-25: too many spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 62-62: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 95-95: wrong indentation: expected 6 but found 5

(indentation)

---

[warning] 98-98: too many spaces before colon

(colons)

---

[warning] 104-104: too many spaces before colon

(colons)

---

[warning] 109-109: too many spaces before colon

(colons)

---

[warning] 114-114: too many spaces before colon

(colons)

---

[warning] 127-127: too many spaces before colon

(colons)

---

[warning] 132-132: too many spaces before colon

(colons)

---

[warning] 137-137: too many spaces before colon

(colons)

---

[warning] 142-142: too many spaces before colon

(colons)

---

[warning] 151-151: too many spaces before colon

(colons)

---

[warning] 156-156: too many spaces before colon

(colons)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: build
* GitHub Check: build

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>config/docker/users.yml (1)</summary>

`5-8`: <details>
<summary>✅ Verification successful</summary>

**Verify consistency of config key across JSON and YAML**  
Ensure that `getEmailFromUserInsteadOfSettings` is spelled identically in `config/docker/ui-config/web-ui.json` and any other configuration files so that flag-driven logic in frontend and backend aligns.

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify consistent key usage across config files
rg -n "getEmailFromUserInsteadOfSettings" -n config/docker
```

Length of output: 214

---



</details>

**No spelling inconsistencies found for `getEmailFromUserInsteadOfSettings`**  
Verified that the flag is defined identically in both:  
- config/docker/users.yml:6  
- config/docker/ui-config/web-ui.json:132  

No changes required.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-27T15:58:57Z
user: login: freddidierRTE
body: Add in documentation in :
- https://opfab.github.io/documentation/current/reference_doc/#_configuring_email_notifications
- https://opfab.github.io/documentation/current/deployment/index.html#ui_properties 
- https://opfab.github.io/documentation/current/deployment/#_users_service
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T08:23:16Z
user: login: vlo-rte
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T09:11:50Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>config/docker/users.yml (1)</summary><blockquote>

`5-7`: **Document configuration breaking change**  
The restructure from a flat `users` section to `operatorfabric.users.default` is a backward-incompatible change. Make sure your migration guide and all deployment artifacts (Ansible playbooks, Helm charts, CI/CD pipelines, examples in README, etc.) are updated accordingly.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>config/docker/users.yml (1)</summary><blockquote>

`8-170`: **YAML formatting consistency**  
YAMLLint reports multiple issues (missing spaces after commas, extra spaces before colons, inconsistent indentation). Please run your linter to fix punctuation spacing and indentation for clarity and to avoid potential parse errors.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[warning] 11-11: too few spaces after comma

(commas)

---

[warning] 13-13: too many spaces before colon

(colons)

---

[warning] 14-14: too many spaces before colon

(colons)

---

[warning] 15-15: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 24-24: too few spaces after comma

(commas)

---

[warning] 25-25: too many spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 62-62: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 95-95: wrong indentation: expected 6 but found 5

(indentation)

---

[warning] 98-98: too many spaces before colon

(colons)

---

[warning] 104-104: too many spaces before colon

(colons)

---

[warning] 109-109: too many spaces before colon

(colons)

---

[warning] 114-114: too many spaces before colon

(colons)

---

[warning] 127-127: too many spaces before colon

(colons)

---

[warning] 132-132: too many spaces before colon

(colons)

---

[warning] 137-137: too many spaces before colon

(colons)

---

[warning] 142-142: too many spaces before colon

(colons)

---

[warning] 151-151: too many spaces before colon

(colons)

---

[warning] 156-156: too many spaces before colon

(colons)

---

[error] 170-170: no new line character at the end of file

(new-line-at-end-of-file)

</details>

</details>

</blockquote></details>
<details>
<summary>src/docs/asciidoc/deployment/configuration/web-ui_configuration.adoc (1)</summary><blockquote>

`201-201`: **Document new UI config flag**  
The documentation for `settings.getEmailFromUserInsteadOfSettings` is accurate. For even better clarity, consider adding a JSON snippet illustrating its use, for example:

[source, json]
----
"settings": {
  "getEmailFromUserInsteadOfSettings": true
}
----

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8f6063196907ed30bb3350062046a7af9c5f0e22 and 40149463f48635c7d8173d522f22147d9a0f3f39.

</details>

<details>
<summary>📒 Files selected for processing (22)</summary>

* `config/docker/ui-config/web-ui.json` (1 hunks)
* `config/docker/users.yml` (1 hunks)
* `config/keycloak/export/dev-users-0.json` (1 hunks)
* `services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java` (1 hunks)
* `services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java` (3 hunks)
* `services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java` (3 hunks)
* `services/users/src/main/java/org/opfab/users/model/User.java` (6 hunks)
* `services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java` (2 hunks)
* `services/users/src/main/modeling/swagger.yaml` (1 hunks)
* `services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java` (8 hunks)
* `services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java` (1 hunks)
* `services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java` (1 hunks)
* `services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java` (2 hunks)
* `services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java` (5 hunks)
* `src/docs/asciidoc/deployment/configuration/configuration.adoc` (2 hunks)
* `src/docs/asciidoc/deployment/configuration/web-ui_configuration.adoc` (1 hunks)
* `src/docs/asciidoc/reference_doc/mail_notification.adoc` (2 hunks)
* `tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java` (3 hunks)
* `ui/main/src/app/components/settings/SettingsComponent.html` (1 hunks)
* `ui/main/src/app/components/settings/SettingsComponent.ts` (1 hunks)
* `ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts` (2 hunks)
* `ui/main/src/app/views/settings/SettingsView.ts` (2 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (2)</summary>

* src/docs/asciidoc/deployment/configuration/configuration.adoc
* src/docs/asciidoc/reference_doc/mail_notification.adoc

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (18)</summary>

* services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java
* services/users/src/main/modeling/swagger.yaml
* services/users/src/test/java/org/opfab/users/services/GroupsServiceShould.java
* services/users/src/main/java/org/opfab/users/controllers/CurrentUserWithPerimetersController.java
* ui/main/src/app/views/settings/SettingsView.ts
* services/users/src/main/java/org/opfab/users/model/User.java
* ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts
* config/keycloak/export/dev-users-0.json
* services/users/src/test/java/org/opfab/users/services/PerimetersServiceShould.java
* config/docker/ui-config/web-ui.json
* services/users/src/test/java/org/opfab/users/services/EntitiesServiceShould.java
* ui/main/src/app/components/settings/SettingsComponent.html
* ui/main/src/app/components/settings/SettingsComponent.ts
* services/users/src/test/java/org/opfab/users/services/CurrentUserWithPerimetersServiceShould.java
* services/users/src/main/java/org/opfab/users/services/CurrentUserWithPerimetersService.java
* tools/spring/spring-oauth2-utilities/src/main/java/org/opfab/springtools/configuration/oauth/jwt/JwtProperties.java
* services/users/src/test/java/org/opfab/users/services/UsersServiceShould.java
* services/users/src/main/java/org/opfab/users/controllers/UserWithPerimetersController.java

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

<details>
<summary>config/docker/users.yml</summary>

[warning] 11-11: too few spaces after comma

(commas)

---

[warning] 13-13: too many spaces before colon

(colons)

---

[warning] 14-14: too many spaces before colon

(colons)

---

[warning] 15-15: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 21-21: too few spaces after comma

(commas)

---

[warning] 24-24: too few spaces after comma

(commas)

---

[warning] 25-25: too many spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 27-27: too few spaces after comma

(commas)

---

[warning] 62-62: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 84-84: too few spaces after comma

(commas)

---

[warning] 95-95: wrong indentation: expected 6 but found 5

(indentation)

---

[warning] 98-98: too many spaces before colon

(colons)

---

[warning] 104-104: too many spaces before colon

(colons)

---

[warning] 109-109: too many spaces before colon

(colons)

---

[warning] 114-114: too many spaces before colon

(colons)

---

[warning] 127-127: too many spaces before colon

(colons)

---

[warning] 132-132: too many spaces before colon

(colons)

---

[warning] 137-137: too many spaces before colon

(colons)

---

[warning] 142-142: too many spaces before colon

(colons)

---

[warning] 151-151: too many spaces before colon

(colons)

---

[warning] 156-156: too many spaces before colon

(colons)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: build
* GitHub Check: build

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>config/docker/users.yml (1)</summary>

`5-6`: **Introduce `operatorfabric.users` parent and new flag**  
The `operatorfabric.users` key now properly nests previous settings under `default`, and the `getEmailFromUserInsteadOfSettings: false` flag is in place to toggle email retrieval behavior.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-28T14:13:01Z
user: login: freddidierRTE
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T15:12:55Z

Review comments:
diff_hunk: @@ -1,167 +1,170 @@
 #  POPULATE THE USER DATABASE ON INIT
 # !!!! WARNING: VALUES SHOULD BE CHANGED FOR PRODUCTION MODE !!!!
 #
-operatorfabric.users.default:
-  users:
-    - login: admin
-      groups: ["ADMIN"]
-      entities: ["ENTITY1_FR","ENTITY2_FR"]
-    - login: operator1_fr
-      firstname : John
-      lastname : Doe
-      groups: ["Dispatcher","Maintainer"]
-      entities: ["ENTITY1_FR"]
-    - login: operator2_fr
-      groups: ["Planner", "Maintainer", "Manager"]
-      entities: ["ENTITY2_FR"]
-    - login: operator3_fr
-      groups: [ "RTE","ADMIN","Maintainer","Dispatcher"]
-      entities: [ "ENTITY3_FR" ]
-    - login: operator4_fr
-      groups: ["Maintainer","Dispatcher"]
-      entities: ["ENTITY1_FR", "ENTITY2_FR", "ENTITY3_FR",  "ENTITY4_FR" ]
-    - login: operator5_fr
-      groups: [ "RTE","ADMIN","Maintainer","Dispatcher"]
-      entities: [ "ENTITY1_FR" ]
-    - login: operator6_fr
-      groups: ["Maintainer"]
-      entities: ["ENTITY1_FR"]
-    - login: itsupervisor1
-      groups: ["ADMIN", "Supervisor"]
-      entities: ["IT_SUPERVISOR_ENTITY"]
-    - login: operator1_crisisRoom
-      groups: ["Supervisor"]
-      entities: ["ENTITY1_FR", "ENTITY2_FR", "ENTITY3_FR", "ENTITY4_FR"]
-    - login: operator1_it
-      groups: ["Dispatcher", "Maintainer"]
-      entities: ["ENTITY1_IT"]
-    - login: operator2_it
-      groups: ["Planner", "Maintainer"]
-      entities: ["ENTITY2_IT"]
-    - login: operator3_it
-      groups: ["Dispatcher", "Planner", "Maintainer"]
-      entities: ["ENTITY3_IT"]
-    - login: operator1_nl
-      groups: ["Dispatcher", "Planner", "Maintainer"]
-      entities: ["ENTITY1_NL"]
-    - login: operator2_nl
-      groups: ["Dispatcher", "Planner", "Maintainer"]
-      entities: ["ENTITY2_NL"]
-    - login: operator1_eu
-      groups: ["Planner", "Maintainer"]
-      entities: ["ENTITY1_EU"]
-    - login: operator2_eu
-      groups: ["Planner", "Maintainer"]
-      entities: ["ENTITY2_EU"]
-    - login: publisher_test
-      groups: ["Dispatcher", "Maintainer", "Manager"]
-    - login: opfab
-      groups: ["Dispatcher", "Maintainer", "Manager","ADMIN"]
-  groups:
-    - id: ADMIN
-      name: ADMINISTRATORS
-      description: The admin group
-      permissions: ["ADMIN"]
-    - id: RTE
-      name: RTE France
-      description: RTE TSO Group
-      realtime: false
-    - id: Dispatcher
-      name: Dispatcher
-      description: Dispatcher Group
-      realtime: true
-    - id: Planner
-      name: Planner
-      description: Planner Group
-      realtime: true
-    - id: Supervisor
-      name: Supervisor
-      description: Supervisor Group
-      realtime: true
-      permissions: ["READONLY","ADMIN_BUSINESS_PROCESS","VIEW_ALL_CARDS"]
-    - id: Manager
-      name: Manager
-      description: Manager Group
-      realtime: false
-      permissions: ["VIEW_ALL_CARDS"]
-    - id: Maintainer
-      name: Maintainer
-      description: Maintainer Group
-      realtime: false
-  entities:
-   - id: ENTITY1_FR
-     name: Control Center FR North
-     description: Control Center FR North
-     parents : ["ENTITY_FR"]
-     labels: ["FR1 label"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY2_FR
-     name: Control Center FR South
-     description: Control Center FR South
-     parents : ["ENTITY_FR"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY3_FR
-     name: Control Center FR East
-     description: Control Center FR East
-     parents : ["ENTITY_FR"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY4_FR
-     name: Control Center FR West
-     description: Control Center FR West
-     parents : ["ENTITY_FR"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY_FR
-     name: French Control Centers
-     description: French Control Centers
-     roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
-   - id: EUROPEAN_SUPERVISION_CENTERS
-     name: European Supervision Centers
-     description: European Supervision Centers
-     roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
-   - id: IT_SUPERVISOR_ENTITY
-     name: IT SUPERVISION CENTER
-     description: IT SUPERVISION CENTER
-     parents : ["EUROPEAN_SUPERVISION_CENTERS"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY1_IT
-     name: Control Center IT North
-     description: Control Center IT North
-     parents : ["ENTITY_IT"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY2_IT
-     name: Control Center IT Center
-     description: Control Center IT Center
-     parents : ["ENTITY_IT"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY3_IT
-     name: Control Center IT South
-     description: Control Center IT South
-     parents : ["ENTITY_IT"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY_IT
-     name: Italian Control Centers
-     description: Italian Control Centers
-     roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
-   - id: ENTITY1_NL
-     name: Control Center NL North
-     description: Control Center NL North
-     parents : ["ENTITY_NL"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY2_NL
-     name: Control Center NL South
-     description: Control Center NL South
-     parents : ["ENTITY_NL"]
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY_NL
-     name: Dutch Control Centers
-     description: Dutch Control Centers
-     roles: ["ACTIVITY_AREA_GROUP", "CARD_RECEIVER"]
-   - id: ENTITY1_EU
-     name: North Europe Control Center
-     description: North Europe Control Center
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
-   - id: ENTITY2_EU
-     name: South Europe Control Center
-     description: South Europe Control Center
-     roles: ["ACTIVITY_AREA", "CARD_RECEIVER", "CARD_SENDER"]
+
+operatorfabric.users:
+  getEmailFromUserInsteadOfSettings: true
+  default:
path: config/docker/users.yml
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Document the configuration breaking change.**

The configuration structure has been significantly changed by introducing the `operatorfabric.users` parent key. This is a breaking change that will require updates to existing deployment configurations.



Consider adding migration documentation or backward compatibility support. Please verify that all deployment scripts and documentation are updated to reflect this new structure.

---



</details>

**Document configuration breaking change in users.yml**

The restructure introduces a new top-level `operatorfabric.users` key and nests all former settings under `operatorfabric.users.default`, which is a breaking change. Please:

- Update migration documentation to guide users from the old flat `users` section to the new nested structure.  
- Review and update all deployment scripts (Ansible/CICD/Helm charts, etc.) and README examples to reference `operatorfabric.users.default` and `getEmailFromUserInsteadOfSettings` correctly.  
- Resolve any YAML formatting issues (indentation, empty values) reported by static analysis.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In config/docker/users.yml around lines 5 to 7, the configuration structure was
changed by adding the operatorfabric.users parent key and nesting settings under
operatorfabric.users.default, which breaks backward compatibility. To fix this,
update the migration documentation to explain how to move from the old flat
users section to the new nested structure. Also, review and modify all
deployment scripts, CI/CD pipelines, Helm charts, and README examples to use the
new operatorfabric.users.default path and the getEmailFromUserInsteadOfSettings
key correctly. Finally, check and correct any YAML formatting issues such as
indentation or empty values to ensure valid syntax.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-27T15:36:05Z
author_association: NONE
diff_hunk: @@ -63,6 +64,13 @@ export class NotificationConfigurationView {
         this.lastProcessesStatesNotifiedByEmailSaved = this.getCheckedStatesForEmailNotificationPerProcesses();
     }
 
+    private isEmailFromUserInsteadOfSettingsAndFieldIsNotEmpty(): boolean {
+        return (
+            ConfigService.getConfigValue('settings.getEmailFromUserInsteadOfSettings', false) &&
+            UsersService.getCurrentUserWithPerimeters().email?.length > 0
+        );
+    }
path: ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add null safety check for getCurrentUserWithPerimeters()**

The method correctly implements the logic but should include a null safety check for the user object before accessing the email property.



Apply this diff to add null safety:

```diff
 private isEmailFromUserInsteadOfSettingsAndFieldIsNotEmpty(): boolean {
+    const currentUser = UsersService.getCurrentUserWithPerimeters();
     return (
         ConfigService.getConfigValue('settings.getEmailFromUserInsteadOfSettings', false) &&
-        UsersService.getCurrentUserWithPerimeters().email?.length > 0
+        currentUser?.email?.length > 0
     );
 }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    private isEmailFromUserInsteadOfSettingsAndFieldIsNotEmpty(): boolean {
        const currentUser = UsersService.getCurrentUserWithPerimeters();
        return (
            ConfigService.getConfigValue('settings.getEmailFromUserInsteadOfSettings', false) &&
            currentUser?.email?.length > 0
        );
    }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
ui/main/src/app/views/notificationConfiguration/NotificationConfigurationView.ts
around lines 67 to 72, the method calls
UsersService.getCurrentUserWithPerimeters() but does not check if the returned
user object is null or undefined before accessing the email property. To fix
this, add a null safety check to ensure the user object exists before accessing
user.email, preventing potential runtime errors.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-27T15:36:05Z
author_association: NONE
diff_hunk: @@ -97,6 +97,8 @@ public AbstractAuthenticationToken convert(Jwt jwt) {
                 if (jwtProperties.isGettingEntitiesFromToken())
                     user.setEntities(getEntitiesFromToken(jwt));
 
+                user.setEmail(jwt.getClaimAsString(jwtProperties.getEmailClaim()));
path: services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java
user: login: freddidierRTE
body: I do not think it is usefull as it is already set from the createUserDataVirtualFromJwt
created_at: 2025-05-28T06:23:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +97,8 @@ public AbstractAuthenticationToken convert(Jwt jwt) {
                 if (jwtProperties.isGettingEntitiesFromToken())
                     user.setEntities(getEntitiesFromToken(jwt));
 
+                user.setEmail(jwt.getClaimAsString(jwtProperties.getEmailClaim()));
path: services/users/src/main/java/org/opfab/users/configuration/oauth2/OAuth2UsersConfiguration.java
user: login: vlo-rte
body: Done.
created_at: 2025-05-28T09:11:49Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-26T13:56:18Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

This change introduces support for managing user email addresses as a first-class property throughout the configuration, backend, and frontend layers. A new configuration flag, `getEmailFromUserInsteadOfSettings`, is added to both JSON and YAML configuration files, enabling conditional retrieval of the user's email from their profile or from settings. The backend `User` model is extended to include an `email` field, with corresponding updates to constructors, equality checks, and API schema. Authentication logic is updated to extract the email from JWT claims. Controller and service classes are modified to propagate the new flag and handle email source selection. The frontend adapts to conditionally display and manage the email field based on this flag, updating input rendering, settings retrieval, and notification logic. Test code is updated to align with the new model and constructor signatures. Documentation is also updated to describe the new configuration property and its effect on email notification behavior.

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=opfab/operatorfabric-core&utm_content=8432):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: vlo-rte
created_at: 2025-05-28T14:04:20Z
body: > Add in documentation in :
> 
>     * https://opfab.github.io/documentation/current/reference_doc/#_configuring_email_notifications
> 
>     * https://opfab.github.io/documentation/current/deployment/index.html#ui_properties
> 
>     * https://opfab.github.io/documentation/current/deployment/#_users_service

Done.
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-28T14:13:22Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=org.lfenergy.operatorfabric%3Aoperatorfabric-core&pullRequest=8432) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=org.lfenergy.operatorfabric%3Aoperatorfabric-core&pullRequest=8432&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=org.lfenergy.operatorfabric%3Aoperatorfabric-core&pullRequest=8432&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=org.lfenergy.operatorfabric%3Aoperatorfabric-core&pullRequest=8432&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [45.2% Coverage on New Code](https://sonarcloud.io/component_measures?id=org.lfenergy.operatorfabric%3Aoperatorfabric-core&pullRequest=8432&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=org.lfenergy.operatorfabric%3Aoperatorfabric-core&pullRequest=8432&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=org.lfenergy.operatorfabric%3Aoperatorfabric-core&pullRequest=8432)


author_association: NONE