Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable 2FA for Rocketchat, as it was not needed and caused random issues on user login #12

Merged
merged 7 commits into from
Oct 2, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.vi.migrationtool.keycloak;

import com.vi.migrationtool.common.MigrationTasks;
import com.vi.migrationtool.config.BeanAwareSpringLiquibase;
import java.util.List;
import liquibase.database.Database;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;

@Data
@Slf4j
public class AddTenantIdAttributeToUsersWithRoleTask extends MigrationTasks {

private static final String SPLIT_CHAR = ",";

private Long tenantId;

private String roleNames;
private JdbcTemplate userServiceJdbcTemplate;

@Override
public void execute(Database database) {
if (roleNames != null) {
KeycloakService keycloakService = BeanAwareSpringLiquibase.getBean(KeycloakService.class);
this.userServiceJdbcTemplate =
BeanAwareSpringLiquibase.getNamedBean("userServiceJdbcTemplate", JdbcTemplate.class);

List<UsersWithRole> updatedUsersPerRole =
keycloakService.addCustomAttributeToUsersWithRole(
"tenantId", tenantId, List.of(roleNames.split(SPLIT_CHAR)));

updateUserTables(updatedUsersPerRole);
} else {
log.warn("No role names provided for task: {}", this.getClass().getSimpleName());
}
}

private void updateUserTables(List<UsersWithRole> updatedUsersPerRole) {

updatedUsersPerRole.forEach(
usersWithRole -> {
log.info("Updating tenantId for users with role: {}", usersWithRole.getRole());
if (UserRole.CONSULTANT.getValue().equals(usersWithRole.getRole())) {
updateConsultantsWithTenantIdIfNotSet(usersWithRole);
}
if (UserRole.USER.getValue().equals(usersWithRole.getRole())) {
updateUsersWithTenantIdIfNotSet(usersWithRole);
}
});
}

private void updateUsersWithTenantIdIfNotSet(UsersWithRole usersWithRole) {
usersWithRole
.getUserIds()
.forEach(
userId -> {
log.info("Set tenantId for {} with id {} to {}", "user", userId, tenantId);
userServiceJdbcTemplate.update(
"UPDATE user SET tenant_id = ? WHERE user_id = ? and tenant_id is null",
tenantId,
userId);
});
}

private void updateConsultantsWithTenantIdIfNotSet(UsersWithRole usersWithRole) {
usersWithRole
.getUserIds()
.forEach(
userId -> {
log.info("Set tenantId for {} with id {} to {}", "consultant", userId, tenantId);
userServiceJdbcTemplate.update(
"UPDATE consultant SET tenant_id = ? WHERE consultant_id = ? and tenant_id is null",
tenantId,
userId);
});
}
}
89 changes: 87 additions & 2 deletions src/main/java/com/vi/migrationtool/keycloak/KeycloakService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.keycloak.representations.idm.RoleRepresentation;
Expand Down Expand Up @@ -38,6 +39,7 @@ public class KeycloakService {
private static final String SEARCH_PARAM = "search";
private static final String MAX_USERS_TO_MIGRATE = "500";
private static final String ADMIN_REALMS = "/admin/realms/";
private static final String PROVIDED_ROLE_DOESNT_EXISTS_IN_KEYCLOAK_MSG = "The provided role {} doesn't exists in keycloak, please create it first";
private final KeycloakConfig keycloakConfig;

public void createRole(String roleName) {
Expand Down Expand Up @@ -76,7 +78,7 @@ public void addRoleToUsers(final List<String> usernames, final String roleName)
Optional<RoleRepresentation> role = getRoleBy(roleName, httpHeaders);
if (role.isEmpty()) {
log.error(
"The provided role {} doesn't exists in keycloak, please create it first", roleName);
PROVIDED_ROLE_DOESNT_EXISTS_IN_KEYCLOAK_MSG, roleName);
}

var restTemplate = new RestTemplate();
Expand Down Expand Up @@ -112,7 +114,7 @@ private void tryToAddRoleToKeycloakUser(
Optional<RoleRepresentation> role = getRoleBy(roleNameDoAdd, httpHeaders);
if (role.isEmpty()) {
log.error(
"The provided role {} doesn't exists in keycloak, please create it first", roleNameDoAdd);
PROVIDED_ROLE_DOESNT_EXISTS_IN_KEYCLOAK_MSG, roleNameDoAdd);
}
keycloakUsers.forEach(
user -> callKeycloakToAddRoleToUser(role.get(), httpHeaders, restTemplate, user));
Expand Down Expand Up @@ -290,6 +292,89 @@ private String getAddRoleToUserBody(final RoleRepresentation role) {
}
}

public List<UsersWithRole> addCustomAttributeToUsersWithRole(
String customAttribute, Long value, List<String> roleNames) {
if (roleNames != null) {
return roleNames.stream()
.map(roleName -> addCustomAttributeToUsersWithRole(customAttribute, value, roleName))
.collect(Collectors.toList());
} else {
log.warn("No role names provided: {}", this.getClass().getSimpleName());
return Collections.emptyList();
}
}

private UsersWithRole addCustomAttributeToUsersWithRole(
String customAttribute, Long value, String roleName) {

var httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
KeycloakLoginResponseDTO loginResponse = loginAdminUser();
httpHeaders.setBearerAuth(loginResponse.getAccessToken());

Optional<RoleRepresentation> role = getRoleBy(roleName, httpHeaders);
if (role.isEmpty()) {
log.error(
PROVIDED_ROLE_DOESNT_EXISTS_IN_KEYCLOAK_MSG, roleName);
}

var restTemplate = new RestTemplate();
restTemplate.setErrorHandler(getResponseErrorHandler());
var users = getUsersWithRoleName(roleName, httpHeaders);
var updatedUsers =
users.stream()
.map(
user ->
addCustomAttributeToUserIfDoesNotExist(
customAttribute, value, httpHeaders, restTemplate, user))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());

return new UsersWithRole(roleName, updatedUsers);
}

private Optional<String> addCustomAttributeToUserIfDoesNotExist(
String customAttribute,
Long customAttributeValue,
HttpHeaders httpHeaders,
RestTemplate restTemplate,
KeycloakUser user) {
var updateUserRolesUrl =
keycloakConfig.getAuthServerUrl()
+ ADMIN_REALMS
+ keycloakConfig.getRealm()
+ "/users/"
+ user.getId();

Map<String, Object> attributes = (Map<String, Object>) user.getAttributes();
if (attributes.containsKey(customAttribute)) {
log.info(
"User {} already has the custom attribute {}, will not override it's value",
user.getUsername(),
customAttribute);
return Optional.empty();
}
attributes.put(customAttribute, customAttributeValue);
try {
restTemplate.exchange(
updateUserRolesUrl, HttpMethod.PUT, new HttpEntity<>(user, httpHeaders), Void.class);
} catch (Exception e) {
log.error(
"Error while adding custom attribute {} = {}, to user {}",
customAttribute,
customAttributeValue,
user.getUsername());
return Optional.empty();
}
log.info(
"Added keycloak attribute {} = {}, to user {}",
customAttribute,
customAttributeValue,
user.getUsername());
return Optional.of(user.getId());
}

private static ResponseErrorHandler getResponseErrorHandler() {
return new ResponseErrorHandler() {
@Override
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/vi/migrationtool/keycloak/UserRole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.vi.migrationtool.keycloak;

import java.util.Arrays;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum UserRole {
ANONYMOUS("anonymous"),
USER("user"),
CONSULTANT("consultant"),
TECHNICAL("technical"),
PEER_CONSULTANT("peer-consultant"),
MAIN_CONSULTANT("main-consultant"),
GROUP_CHAT_CONSULTANT("group-chat-consultant"),
USER_ADMIN("user-admin"),
SINGLE_TENANT_ADMIN("single-tenant-admin"),
TENANT_ADMIN("tenant-admin"),
AGENCY_ADMIN("agency-admin"),
RESTRICTED_AGENCY_ADMIN("restricted-agency-admin"),
TOPIC_ADMIN("topic-admin"),
NOTIFICATIONS_TECHNICAL("notifications-technical");

private final String value;

public static Optional<UserRole> getRoleByValue(String value) {
return Arrays.stream(values()).filter(userRole -> userRole.value.equals(value)).findFirst();
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/vi/migrationtool/keycloak/UsersWithRole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.vi.migrationtool.keycloak;

import java.util.Collection;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;

@AllArgsConstructor
@Getter
public class UsersWithRole {

@NonNull String role;

@NonNull Collection<String> userIds;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">

<changeSet author="tkuzynow" id="disable_rocketchat_2fa">
<customChange class="com.vi.migrationtool.rocketchat.RocketChatConfigurationUpdateTask">
<param name="rocketChatMethod" value='saveSettings'/>
<param name="rocketChatRequest"
value='{"message":"{\"msg\":\"method\",\"id\":\"\",\"method\":\"saveSettings\",\"params\":[[{\"_id\":\"Accounts_TwoFactorAuthentication_Enabled\",\"value\":false}]]}"}'/>
</customChange>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">

<changeSet author="tkuzynow" id="add_tenant_id_to_users_without_tenant_id">
<customChange class="com.vi.migrationtool.keycloak.AddTenantIdAttributeToUsersWithRoleTask">
<param name="tenantId" value='1'/>
<param name="roleNames" value='consultant,user'/>
</customChange>
</changeSet>
</databaseChangeLog>
1 change: 1 addition & 0 deletions src/main/resources/migrations/master-diakonie.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
<include file="migrations/changeset/0014_import_dioceses_from_agency_to_tenant_service/0014_import_dioceses_from_agency_to_tenant_service.xml"/>
<include file="migrations/changeset/0016_migrate_consulting_types_description_to_topic/0016_migrate_consulting_types_description_to_topic.xml"/>
<include file="migrations/changeset/0018_migrate_tenant_admins_to_set_user_admin_and_agency_admin_roles/0018_migrate_tenant_admins_to_set_user_admin_and_agency_admin_roles.xml"/>
<include file="migrations/changeset/0021_add_custom_keycloak_attribute_to_users_with_role/0021_add_custom_keycloak_attribute_to_users_with_role.xml"/>
</databaseChangeLog>
8 changes: 5 additions & 3 deletions src/main/resources/migrations/master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
<include file="migrations/changeset/0013_add_videogroupchat_release_toggle/0013_add_videogroupchat_release_toggle.xml"/>
<include file="migrations/changeset/0014_import_dioceses_from_agency_to_tenant_service/0014_import_dioceses_from_agency_to_tenant_service.xml"/>
<include file="migrations/changeset/0015_create_jitsi_technical_keycloak_role/0015_create_jitsi_technical_keycloak_role.xml"/>
<!-- by default migration 0015_add_groupchat_role_for_consultants is not applied. This must be decided by each of the clients and activated if needed -->
<!-- by default migraiton 0016_migrate_consulting_types_description_to_topic. This must be decided by each of the clients and activated if needed -->
<!-- by default migration 0017_migrate_agency_default_counselling_relations is not applied. This must be decided by each of the clients and activated if needed -->
<!-- by default migration 0015_add_groupchat_role_for_consultants is not applied. This must be decided by each of the clients and activated if needed -->
<!-- by default migraiton 0016_migrate_consulting_types_description_to_topic. This must be decided by each of the clients and activated if needed -->
<!-- by default migration 0017_migrate_agency_default_counselling_relations is not applied. This must be decided by each of the clients and activated if needed -->
<!-- by default migration 0018_migrate_tenant_admins_to_set_user_admin_and_agency_admin_roles is not applied. This must be decided by each of the clients and activated if needed -->
<!-- by default migration 0019_add_counselling_relation_release_toggle is not applied. This must be decided by each of the clients and activated if needed -->
<!-- by default migration 0020_disable_rocketchat_2fa is not applied. This must be decided by each of the clients and activated if needed -->
<!-- by default migration 0021_add_custom_keycloak_attribute_to_users_with_role is not applied. This must be decided by each of the clients and activated if needed -->
</databaseChangeLog>