Skip to content

Commit e393b45

Browse files
Merge pull request #13149 from SORMAS-Foundation/#13080-Keycloak-username-modification-is-not-synced-by-sormas
#13080 keycloak username modification is not synced by sormas
2 parents 0c55702 + b291893 commit e393b45

File tree

9 files changed

+73
-13
lines changed

9 files changed

+73
-13
lines changed

sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2677,6 +2677,7 @@ public interface Captions {
26772677
String User_associatedOfficer = "User.associatedOfficer";
26782678
String User_community = "User.community";
26792679
String User_district = "User.district";
2680+
String User_externalId = "User.externalId";
26802681
String User_hasConsentedToGdpr = "User.hasConsentedToGdpr";
26812682
String User_healthFacility = "User.healthFacility";
26822683
String User_laboratory = "User.laboratory";

sormas-api/src/main/java/de/symeda/sormas/api/user/UserDto.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public class UserDto extends EntityDto {
6767
public static final String LIMITED_DISEASES = "limitedDiseases";
6868
public static final String LANGUAGE = "language";
6969
public static final String HAS_CONSENTED_TO_GDPR = "hasConsentedToGdpr";
70+
public static final String EXTERNAL_ID = "externalId";
7071
public static final String JURISDICTION_LEVEL = "jurisdictionLevel";
7172

7273
private boolean active = true;
@@ -107,6 +108,8 @@ public class UserDto extends EntityDto {
107108

108109
private boolean hasConsentedToGdpr;
109110

111+
private String externalId;
112+
110113
private JurisdictionLevel jurisdictionLevel;
111114

112115
public static UserDto build() {
@@ -278,6 +281,14 @@ public void setHasConsentedToGdpr(boolean hasConsentedToGdpr) {
278281
this.hasConsentedToGdpr = hasConsentedToGdpr;
279282
}
280283

284+
public String getExternalId() {
285+
return externalId;
286+
}
287+
288+
public void setExternalId(String externalId) {
289+
this.externalId = externalId;
290+
}
291+
281292
public JurisdictionLevel getJurisdictionLevel() {
282293
return jurisdictionLevel;
283294
}

sormas-api/src/main/resources/captions.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,6 +2606,7 @@ User.uuid=UUID
26062606
User.region=Region
26072607
User.district=District
26082608
User.community=Community
2609+
User.externalId=External ID
26092610
userRestrictDiseases=Restrict access to specific diseases
26102611
# Vaccination
26112612
vaccinationNewVaccination=New vaccination

sormas-backend/src/main/java/de/symeda/sormas/backend/user/KeycloakService.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Optional;
28-
import java.util.Set;
2928
import java.util.stream.Collectors;
3029

3130
import javax.annotation.PostConstruct;
@@ -60,6 +59,7 @@
6059
import de.symeda.sormas.api.AuthProvider;
6160
import de.symeda.sormas.api.Language;
6261
import de.symeda.sormas.api.user.UserRight;
62+
import de.symeda.sormas.api.utils.DataHelper.Pair;
6363
import de.symeda.sormas.backend.common.ConfigFacadeEjb.ConfigFacadeEjbLocal;
6464
import de.symeda.sormas.backend.user.event.PasswordResetEvent;
6565
import de.symeda.sormas.backend.user.event.SyncUsersFromProviderEvent;
@@ -241,21 +241,32 @@ public void handleSyncUsersFromProviderEvent(@Observes SyncUsersFromProviderEven
241241
}
242242

243243
List<User> existingUsers = syncUsersFromProviderEvent.getExistingUsers();
244-
Map<String, User> existingUsersByUsername =
245-
existingUsers.stream().collect(Collectors.toMap(user1 -> user1.getUserName().toLowerCase(), Functions.identity()));
244+
Map<String, User> existingUsersByExternalId =
245+
existingUsers.stream().collect(Collectors.toMap(User::getExternalId, Functions.identity(), (u1, u2) -> u1));
246246
List<UserRepresentation> providerUsers = keycloak.get().realm(REALM_NAME).users().list();
247-
List<User> syncedUsers = providerUsers.stream().map(user -> {
248-
User sormasUser = existingUsersByUsername.get(user.getUsername().toLowerCase());
247+
List<User> syncedUsers = providerUsers.stream().map(userRepresentation -> {
248+
String userNameId = userRepresentation.getId();
249+
250+
User sormasUser = existingUsersByExternalId.get(userNameId);
249251
if (sormasUser == null) {
250-
sormasUser = new User();
252+
sormasUser =
253+
existingUsers.stream().filter(u -> u.getUserName().equals(userRepresentation.getUsername())).findFirst().orElse(new User());
251254
}
252-
updateUser(sormasUser, user);
255+
updateUser(sormasUser, userRepresentation);
253256

254257
return sormasUser;
255258
}).collect(Collectors.toList());
256259

257-
Set<String> providerUserNames = providerUsers.stream().map(UserRepresentation::getUsername).collect(Collectors.toSet());
258-
List<User> deletedUsers = existingUsers.stream().filter(user -> !providerUserNames.contains(user.getUserName())).collect(Collectors.toList());
260+
List<Pair<String, String>> providerUserIdentifiers =
261+
providerUsers.stream().map(ur -> Pair.createPair(ur.getId(), ur.getUsername())).collect(Collectors.toList());
262+
List<User> deletedUsers = existingUsers.stream()
263+
.filter(
264+
u -> providerUserIdentifiers.stream()
265+
.noneMatch(
266+
ui -> StringUtils.isNotBlank(u.getExternalId())
267+
? ui.getElement0().equals(u.getExternalId())
268+
: ui.getElement1().equals(u.getUserName())))
269+
.collect(Collectors.toList());
259270

260271
syncUsersFromProviderEvent.getCallback().accept(syncedUsers, deletedUsers);
261272

@@ -307,6 +318,8 @@ private UserRepresentation createUserRepresentation(User user, String hashedPass
307318
}
308319

309320
private void updateUser(User user, UserRepresentation userRepresentation) {
321+
user.setUserName(userRepresentation.getUsername());
322+
user.setExternalId(userRepresentation.getId());
310323
user.setActive(userRepresentation.isEnabled());
311324
user.setUserName(userRepresentation.getUsername());
312325
user.setFirstName(userRepresentation.getFirstName());

sormas-backend/src/main/java/de/symeda/sormas/backend/user/User.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ public class User extends AbstractDomainObject {
136136

137137
private boolean hasConsentedToGdpr;
138138

139+
private String externalId;
140+
139141
@Column(nullable = false, length = CHARACTER_LIMIT_DEFAULT)
140142
public String getUserName() {
141143
return userName;
@@ -348,6 +350,15 @@ public void setHasConsentedToGdpr(boolean hasConsentedToGdpr) {
348350
this.hasConsentedToGdpr = hasConsentedToGdpr;
349351
}
350352

353+
@Column(length = CHARACTER_LIMIT_DEFAULT)
354+
public String getExternalId() {
355+
return externalId;
356+
}
357+
358+
public void setExternalId(String externalId) {
359+
this.externalId = externalId;
360+
}
361+
351362
/**
352363
* Checks if the User possesses any of the specified userRoles
353364
*/

sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserFacadeEjb.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ public static UserDto toDto(User source) {
230230
target.setLimitedDiseases(source.getLimitedDiseases());
231231
target.setLanguage(source.getLanguage());
232232
target.setHasConsentedToGdpr(source.isHasConsentedToGdpr());
233+
target.setExternalId(source.getExternalId());
233234

234235
target.setUserRoles(source.getUserRoles().stream().map(UserRoleFacadeEjb::toReferenceDto).collect(Collectors.toSet()));
235236
target.setJurisdictionLevel(source.getJurisdictionLevel());
@@ -864,6 +865,7 @@ private User fillOrBuildEntity(UserDto source, User target, boolean checkChangeD
864865
target.setLimitedDiseases(source.getLimitedDiseases());
865866
target.setLanguage(source.getLanguage());
866867
target.setHasConsentedToGdpr(source.isHasConsentedToGdpr());
868+
target.setExternalId(source.getExternalId());
867869

868870
fillEntityUserRoles(target, source);
869871

sormas-backend/src/main/resources/sql/sormas_schema.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13242,4 +13242,11 @@ ALTER TABLE selfreports_history
1324213242

1324313243
INSERT INTO schema_version (version_number, comment) VALUES (549, '#13083 Add a manual processing for self Reporting');
1324413244

13245+
-- 2024-09-24 #13080 Keycloak username modification is not synced by sormas
13246+
13247+
ALTER TABLE users ADD COLUMN externalid text;
13248+
ALTER TABLE users_history ADD COLUMN externalid text;
13249+
13250+
INSERT INTO schema_version (version_number, comment) VALUES (550, '#13080 Keycloak username modification is not synced by sormas');
13251+
1324513252
-- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. ***

sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserEditForm.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public class UserEditForm extends AbstractEditForm<UserDto> {
9494

9595
//@formatter:off
9696
private static final String HTML_LAYOUT =
97-
loc(UserDto.UUID) +
97+
fluidRowLocs(UserDto.UUID, UserDto.EXTERNAL_ID) +
9898
loc(PERSON_DATA_HEADING_LOC) +
9999
fluidRowLocs(UserDto.FIRST_NAME, UserDto.LAST_NAME) +
100100
fluidRowLocs(UserDto.USER_EMAIL, UserDto.PHONE) +
@@ -135,7 +135,10 @@ protected void addFields() {
135135

136136
TextField uuid = addField(UserDto.UUID, TextField.class);
137137
uuid.setReadOnly(true);
138-
138+
139+
TextField externalId = addField(UserDto.EXTERNAL_ID, TextField.class);
140+
externalId.setReadOnly(true);
141+
139142
Label personDataHeadingLabel = new Label(I18nProperties.getString(Strings.headingPersonData));
140143
personDataHeadingLabel.addStyleName(H3);
141144
getContent().addComponent(personDataHeadingLabel, PERSON_DATA_HEADING_LOC);

sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserGrid.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
*******************************************************************************/
1818
package de.symeda.sormas.ui.user;
1919

20+
import java.util.ArrayList;
21+
import java.util.Arrays;
2022
import java.util.Collection;
23+
import java.util.List;
2124
import java.util.Set;
2225
import java.util.concurrent.atomic.AtomicBoolean;
2326

2427
import com.vaadin.icons.VaadinIcons;
2528
import com.vaadin.ui.renderers.HtmlRenderer;
2629
import com.vaadin.ui.renderers.TextRenderer;
2730

31+
import de.symeda.sormas.api.AuthProvider;
2832
import de.symeda.sormas.api.FacadeProvider;
2933
import de.symeda.sormas.api.i18n.I18nProperties;
3034
import de.symeda.sormas.api.user.UserCriteria;
@@ -60,7 +64,8 @@ public UserGrid() {
6064

6165
addEditColumn(e -> ControllerProvider.getUserController().edit(e));
6266

63-
setColumns(
67+
List<String> columns = new ArrayList(
68+
Arrays.asList(
6469
ACTION_BTN_ID,
6570
UserDto.UUID,
6671
UserDto.ACTIVE,
@@ -70,7 +75,13 @@ public UserGrid() {
7075
UserDto.USER_EMAIL,
7176
UserDto.ADDRESS,
7277
UserDto.DISTRICT,
73-
UserDto.HEALTH_FACILITY);
78+
UserDto.HEALTH_FACILITY));
79+
80+
if (!AuthProvider.SORMAS.equalsIgnoreCase(FacadeProvider.getConfigFacade().getAuthenticationProvider())) {
81+
columns.add(UserDto.EXTERNAL_ID);
82+
}
83+
84+
setColumns(columns);
7485

7586
((Column<UserDto, String>) getColumn(UserDto.UUID)).setRenderer(new UuidRenderer());
7687
Column<UserDto, Set<UserRoleDto>> userRolesColumn = ((Column<UserDto, Set<UserRoleDto>>) getColumn(UserDto.USER_ROLES));

0 commit comments

Comments
 (0)