Skip to content

Commit

Permalink
Merge pull request #87 from kanton-bern/feature/HELLODATA-1689_user_d…
Browse files Browse the repository at this point in the history
…eactivation

Feature/hellodata 1689 user deactivation
  • Loading branch information
wieczorslawo authored Sep 16, 2024
2 parents cf02c5f + 9605e6a commit 3b6a9c6
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 179 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ public enum HDEvent {
PUBLISH_ROLE_RESOURCES(METAINFO_STREAM, "publish_resources.role", RoleResource.class),
PUBLISH_PERMISSION_RESOURCES(METAINFO_STREAM, "publish_resources.permission", PermissionResource.class),
PUBLISH_PIPELINE_RESOURCES(METAINFO_STREAM, "publish_resources.pipeline", PipelineResource.class),
PUBLISH_USER_RESOURCES(METAINFO_STREAM, "publish_resources.user", UserResource.class), CREATE_USER(METAINFO_STREAM, "create_user", SubsystemUserUpdate.class),
PUBLISH_USER_RESOURCES(METAINFO_STREAM, "publish_resources.user", UserResource.class),
DISABLE_USER(METAINFO_STREAM, "disable_user", SubsystemUserUpdate.class),
ENABLE_USER(METAINFO_STREAM, "enable_user", SubsystemUserUpdate.class),
CREATE_USER(METAINFO_STREAM, "create_user", SubsystemUserUpdate.class),
UPDATE_USER_CONTEXT_ROLE(METAINFO_STREAM, "update_user_context_role", UserContextRoleUpdate.class),
UPDATE_STORAGE_MONITORING_RESULT(METAINFO_STREAM, "update_storage_monitoring_result", StorageMonitoringResult.class),
DELETE_USER(METAINFO_STREAM, "delete_user", SubsystemUserDelete.class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,7 @@
import ch.bedag.dap.hellodata.portal.metainfo.service.MetaInfoResourceService;
import ch.bedag.dap.hellodata.portal.role.data.RoleDto;
import ch.bedag.dap.hellodata.portal.role.service.RoleService;
import ch.bedag.dap.hellodata.portal.user.data.AdUserDto;
import ch.bedag.dap.hellodata.portal.user.data.ContextDto;
import ch.bedag.dap.hellodata.portal.user.data.ContextsDto;
import ch.bedag.dap.hellodata.portal.user.data.DashboardsDto;
import ch.bedag.dap.hellodata.portal.user.data.DataDomainDto;
import ch.bedag.dap.hellodata.portal.user.data.UpdateContextRolesForUserDto;
import ch.bedag.dap.hellodata.portal.user.data.UserContextRoleDto;
import ch.bedag.dap.hellodata.portal.user.data.UserDto;
import ch.bedag.dap.hellodata.portal.user.data.*;
import ch.bedag.dap.hellodata.portalcommon.role.entity.UserContextRoleEntity;
import ch.bedag.dap.hellodata.portalcommon.user.entity.UserEntity;
import ch.bedag.dap.hellodata.portalcommon.user.repository.UserRepository;
Expand All @@ -67,24 +60,6 @@
import io.nats.client.Message;
import jakarta.persistence.EntityExistsException;
import jakarta.ws.rs.NotFoundException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.BooleanUtils;
Expand All @@ -99,6 +74,16 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ResponseStatusException;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static ch.bedag.dap.hellodata.commons.SlugifyUtil.DASHBOARD_ROLE_PREFIX;

@Log4j2
Expand Down Expand Up @@ -160,7 +145,7 @@ public void validateEmailAlreadyExists(String email) {

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void syncAllUsers() {
List<UserDto> allUsers = getAllUsers();
List<UserDto> allUsers = getAllUsers().stream().filter(UserDto::getEnabled).toList();
log.info("[syncAllUsers] Found {} users to sync with surrounding systems.", allUsers.size());
AtomicInteger counter = new AtomicInteger();
allUsers.forEach(u -> {
Expand Down Expand Up @@ -200,11 +185,11 @@ public List<UserDto> getAllUsers() {
}
}
return userRepresentationList.stream()
.filter(userRepresentation -> userRepresentation.getEmail() != null)
.map(userRepresentation -> modelMapper.map(userRepresentation, UserDto.class))
.map(userDto -> fetchAdditionalDataFromPortal(userDto,
allPortalUsers.stream().filter(userEntity -> idEquals(userDto, userEntity)).findFirst()))
.toList();
.filter(userRepresentation -> userRepresentation.getEmail() != null)
.map(userRepresentation -> modelMapper.map(userRepresentation, UserDto.class))
.map(userDto -> fetchAdditionalDataFromPortal(userDto,
allPortalUsers.stream().filter(userEntity -> idEquals(userDto, userEntity)).findFirst()))
.toList();
}

@Nullable
Expand Down Expand Up @@ -257,14 +242,23 @@ public void updateLastAccess(String userId) {
}

public void createUserInSubsystems(String userId) {
UserRepresentation representation = getUserRepresentation(userId);
SubsystemUserUpdate createUser = getSubsystemUserUpdate(userId);
natsSenderService.publishMessageToJetStream(HDEvent.CREATE_USER, createUser);
}

private SubsystemUserUpdate getSubsystemUserUpdate(UserRepresentation representation) {
SubsystemUserUpdate createUser = new SubsystemUserUpdate();
createUser.setFirstName(representation.getFirstName());
createUser.setLastName(representation.getLastName());
createUser.setUsername(representation.getUsername());
createUser.setEmail(representation.getEmail().toLowerCase(Locale.ROOT));
createUser.setActive(representation.isEnabled());
natsSenderService.publishMessageToJetStream(HDEvent.CREATE_USER, createUser);
return createUser;
}

private SubsystemUserUpdate getSubsystemUserUpdate(String userId) {
UserRepresentation representation = getUserRepresentation(userId);
return getSubsystemUserUpdate(representation);
}

@Transactional
Expand All @@ -275,6 +269,10 @@ public void disableUserById(String userId) {
UserRepresentation representation = userResource.toRepresentation();
representation.setEnabled(false);
userResource.update(representation);
userResource.logout();
SubsystemUserUpdate subsystemUserUpdate = getSubsystemUserUpdate(representation);
subsystemUserUpdate.setActive(false);
natsSenderService.publishMessageToJetStream(HDEvent.DISABLE_USER, subsystemUserUpdate);
emailNotificationService.notifyAboutUserDeactivation(representation.getFirstName(), representation.getEmail());
}

Expand All @@ -286,6 +284,11 @@ public void enableUserById(String userId) {
UserRepresentation representation = userResource.toRepresentation();
representation.setEnabled(true);
userResource.update(representation);
SubsystemUserUpdate subsystemUserUpdate = getSubsystemUserUpdate(representation);
subsystemUserUpdate.setActive(true);
natsSenderService.publishMessageToJetStream(HDEvent.ENABLE_USER, subsystemUserUpdate);
UserEntity userEntity = userRepository.getByIdOrAuthId(userId);
synchronizeContextRolesWithSubsystems(userEntity);
emailNotificationService.notifyAboutUserActivation(representation.getFirstName(), representation.getEmail());
}

Expand Down Expand Up @@ -350,13 +353,13 @@ private void updateContextRoles(UUID userId, UpdateContextRolesForUserDto update
private void setRoleForAllRemainingDataDomainsToNone(UpdateContextRolesForUserDto updateContextRolesForUserDto, UserEntity userEntity) {
List<HdContextEntity> allDataDomains = contextRepository.findAllByTypeIn(List.of(HdContextType.DATA_DOMAIN));
List<HdContextEntity> ddDomainsWithoutRoleForUser = allDataDomains.stream()
.filter(availableDD -> updateContextRolesForUserDto.getDataDomainRoles()
.stream()
.noneMatch(ddRole -> ddRole.getContext()
.getContextKey()
.equalsIgnoreCase(
availableDD.getContextKey())))
.toList();
.filter(availableDD -> updateContextRolesForUserDto.getDataDomainRoles()
.stream()
.noneMatch(ddRole -> ddRole.getContext()
.getContextKey()
.equalsIgnoreCase(
availableDD.getContextKey())))
.toList();
if (!ddDomainsWithoutRoleForUser.isEmpty()) {
Optional<RoleDto> first = roleService.getAll().stream().filter(roleDto -> HdRoleName.NONE.name().equalsIgnoreCase(roleDto.getName())).findFirst();
if (first.isPresent()) {
Expand Down Expand Up @@ -558,10 +561,10 @@ public Set<UserContextRoleEntity> getCurrentUserDataDomainRolesWithoutNone() {
}
Optional<UserEntity> userEntity = Optional.of(getUserEntity(currentUserId));
return userEntity.map(user -> user.getContextRoles()
.stream()
.filter(userContextRoleEntity -> HdContextType.DATA_DOMAIN.equals(userContextRoleEntity.getRole().getContextType()))
.filter(userContextRoleEntity -> !HdRoleName.NONE.equals(userContextRoleEntity.getRole().getName()))
.collect(Collectors.toSet())).orElse(Collections.emptySet());
.stream()
.filter(userContextRoleEntity -> HdContextType.DATA_DOMAIN.equals(userContextRoleEntity.getRole().getContextType()))
.filter(userContextRoleEntity -> !HdRoleName.NONE.equals(userContextRoleEntity.getRole().getName()))
.collect(Collectors.toSet())).orElse(Collections.emptySet());
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,24 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.nats.client.Connection;
import jakarta.ws.rs.NotFoundException;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.UserRepresentation;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.modelmapper.ModelMapper;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Collections;
import java.util.List;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;

@Log4j2
@SuppressWarnings("unused")
Expand Down Expand Up @@ -150,6 +142,7 @@ public void testSyncAllUsers() {
when(userRepresentation.getFirstName()).thenReturn(firstName);
when(userRepresentation.getLastName()).thenReturn(lastName);
when(userRepresentation.getId()).thenReturn(createdUserId);
when(userRepresentation.isEnabled()).thenReturn(true);
when(keycloakService.getUserRepresentationById(any())).thenReturn(userRepresentation);
when(userRepository.existsByIdOrAuthId(any(UUID.class), any(String.class))).thenReturn(false);
when(userRepository.findAll()).thenReturn(List.of(userEntity));
Expand Down Expand Up @@ -220,6 +213,7 @@ public void testDisableUserById_UserFound() {
UserResource userResourceMock = mock(UserResource.class, Mockito.RETURNS_DEEP_STUBS);
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setEnabled(true);
userRepresentation.setEmail("some_email@example.com");
UserEntity userEntity = new UserEntity();
userEntity.setId(uuid);

Expand Down Expand Up @@ -260,6 +254,7 @@ public void testEnableUserById_UserFound() {
UserResource userResourceMock = mock(UserResource.class, Mockito.RETURNS_DEEP_STUBS);
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setEnabled(false);
userRepresentation.setEmail("some_email@example.com");
UserEntity userEntity = new UserEntity();
userEntity.setId(uuid);
when(userRepository.getByIdOrAuthId(any(String.class))).thenReturn(userEntity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import static ch.bedag.dap.hellodata.commons.sidecars.events.HDEvent.CREATE_USER;
import static ch.bedag.dap.hellodata.sidecars.airflow.service.user.AirflowUserUtil.toAirflowUser;

@Log4j2
@Service
Expand All @@ -54,17 +55,7 @@ public class AirflowCreateUserConsumer {
private final AirflowUserResourceProviderService userResourceProviderService;
private final AirflowClientProvider airflowClientProvider;

@NotNull
private static AirflowUser toAirflowUser(SubsystemUserUpdate supersetUserCreate) {
AirflowUser airflowUser = new AirflowUser();
airflowUser.setEmail(supersetUserCreate.getEmail());
airflowUser.setRoles(new ArrayList<>()); // Default User-Roles are defined in Airflow-Config
airflowUser.setUsername(supersetUserCreate.getUsername());
airflowUser.setFirstName(supersetUserCreate.getFirstName());
airflowUser.setLastName(supersetUserCreate.getLastName());
airflowUser.setPassword(supersetUserCreate.getUsername()); // Login will be handled by Keycloak
return airflowUser;
}


@SuppressWarnings("unused")
@JetStreamSubscribe(event = CREATE_USER)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package ch.bedag.dap.hellodata.sidecars.airflow.service.user;

import ch.bedag.dap.hellodata.commons.nats.annotation.JetStreamSubscribe;
import ch.bedag.dap.hellodata.commons.sidecars.resources.v1.user.data.SubsystemUserUpdate;
import ch.bedag.dap.hellodata.sidecars.airflow.client.AirflowClient;
import ch.bedag.dap.hellodata.sidecars.airflow.client.user.response.AirflowRole;
import ch.bedag.dap.hellodata.sidecars.airflow.client.user.response.AirflowUserResponse;
import ch.bedag.dap.hellodata.sidecars.airflow.client.user.response.AirflowUsersResponse;
import ch.bedag.dap.hellodata.sidecars.airflow.service.provider.AirflowClientProvider;
import ch.bedag.dap.hellodata.sidecars.airflow.service.resource.AirflowUserResourceProviderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import static ch.bedag.dap.hellodata.commons.sidecars.events.HDEvent.DISABLE_USER;
import static ch.bedag.dap.hellodata.sidecars.airflow.service.user.AirflowUserUtil.*;

@Log4j2
@Service
@RequiredArgsConstructor
@SuppressWarnings("java:S3516")
public class AirflowDisableUserConsumer {

private final AirflowUserResourceProviderService userResourceProviderService;
private final AirflowClientProvider airflowClientProvider;
private final AirflowUserResourceProviderService airflowUserResourceProviderService;


@SuppressWarnings("unused")
@JetStreamSubscribe(event = DISABLE_USER)
public CompletableFuture<Void> disableUser(SubsystemUserUpdate supersetUserUpdate) {
try {
log.info("------- Received airflow user disable request {}", supersetUserUpdate);

AirflowClient airflowClient = airflowClientProvider.getAirflowClientInstance();
AirflowUsersResponse users = airflowClient.users();
List<AirflowRole> allAirflowRoles = CollectionUtils.emptyIfNull(airflowClient.roles().getRoles()).stream().toList();

// Airflow only allows unique username and email, so we make sure there is nobody with either of these already existing, before creating a new one
Optional<AirflowUserResponse> userResult = users.getUsers()
.stream()
.filter(user -> user.getEmail().equalsIgnoreCase(supersetUserUpdate.getEmail()) ||
user.getUsername().equalsIgnoreCase(supersetUserUpdate.getUsername()))
.findFirst();

if (userResult.isPresent()) {
AirflowUserResponse airflowUser = userResult.get();
removeRoleFromUser(airflowUser, ADMIN_ROLE_NAME, allAirflowRoles);
removeRoleFromUser(airflowUser, VIEWER_ROLE_NAME, allAirflowRoles);
removeRoleFromUser(airflowUser, AF_OPERATOR_ROLE_NAME, allAirflowRoles);
removeAllDataDomainRolesFromUser(airflowUser);
addRoleToUser(airflowUser, PUBLIC_ROLE_NAME, allAirflowRoles);
updateUser(airflowUser, airflowClient, airflowUserResourceProviderService);
userResourceProviderService.publishUsers();
log.info("User with email: {} disabled", supersetUserUpdate.getEmail());
} else {
log.warn("User with email: {} not found", supersetUserUpdate.getEmail());
}
} catch (URISyntaxException | IOException e) {
log.error("Could not disable user {}", supersetUserUpdate.getEmail(), e);
}
return null;//NOSONAR
}

}
Loading

0 comments on commit 3b6a9c6

Please sign in to comment.