Skip to content

Commit

Permalink
Merge branch 'master' into bug/MODROLESKC-262-resolve-missing-permission
Browse files Browse the repository at this point in the history
  • Loading branch information
yauhen-vavilkin authored Jan 24, 2025
2 parents 01251ce + 3c42d6a commit f5d0244
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import org.folio.roles.domain.entity.CapabilityEntity;
Expand Down Expand Up @@ -101,6 +102,9 @@ SELECT COUNT(DISTINCT capability.*) FROM (
@Query("select entity from CapabilityEntity entity where entity.name in :names order by entity.name")
List<CapabilityEntity> findAllByNames(@Param("names") Collection<String> names);

@Query("select entity from CapabilityEntity entity where entity.name = :name")
Optional<CapabilityEntity> findByName(@Param("name") String name);

@Query("select distinct entity.id from CapabilityEntity entity where entity.id in :ids order by entity.id")
Set<UUID> findCapabilityIdsByIdIn(@Param("ids") Collection<UUID> capabilityIds);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;
import org.folio.roles.domain.entity.LoadablePermissionEntity;
import org.folio.roles.domain.entity.key.LoadablePermissionKey;
import org.springframework.stereotype.Repository;

@Repository
public interface LoadablePermissionRepository extends BaseCqlJpaRepository<LoadablePermissionEntity, UUID> {
public interface LoadablePermissionRepository
extends BaseCqlJpaRepository<LoadablePermissionEntity, LoadablePermissionKey> {

List<LoadablePermissionEntity> findAllByPermissionNameIn(Collection<String> permissionNames);

Stream<LoadablePermissionEntity> findAllByCapabilityId(UUID capabilityId);

Stream<LoadablePermissionEntity> findAllByCapabilitySetId(UUID capabilitySetId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import static java.util.Optional.empty;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.folio.common.utils.permission.PermissionUtils.hasRequiredFields;
import static org.folio.roles.utils.CapabilityUtils.getCapabilityName;

import jakarta.persistence.EntityExistsException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
Expand All @@ -26,16 +28,19 @@
import org.folio.common.utils.permission.PermissionUtils;
import org.folio.roles.domain.dto.Capability;
import org.folio.roles.domain.dto.CapabilitySet;
import org.folio.roles.domain.entity.LoadablePermissionEntity;
import org.folio.roles.domain.entity.RoleCapabilityEntity;
import org.folio.roles.domain.entity.RoleCapabilitySetEntity;
import org.folio.roles.domain.entity.UserCapabilityEntity;
import org.folio.roles.domain.entity.UserCapabilitySetEntity;
import org.folio.roles.domain.entity.key.LoadablePermissionKey;
import org.folio.roles.domain.model.CapabilityReplacements;
import org.folio.roles.domain.model.event.CapabilitySetEvent;
import org.folio.roles.integration.kafka.mapper.CapabilitySetMapper;
import org.folio.roles.integration.kafka.model.CapabilityEvent;
import org.folio.roles.integration.kafka.model.FolioResource;
import org.folio.roles.integration.kafka.model.Permission;
import org.folio.roles.repository.LoadablePermissionRepository;
import org.folio.roles.repository.RoleCapabilityRepository;
import org.folio.roles.repository.RoleCapabilitySetRepository;
import org.folio.roles.repository.UserCapabilityRepository;
Expand Down Expand Up @@ -66,6 +71,7 @@ public class CapabilityReplacementsService {
private final CapabilityService capabilityService;
private final CapabilitySetService capabilitySetService;
private final CapabilitySetMapper capabilitySetMapper;
private final LoadablePermissionRepository loadablePermissionRepository;

/**
* Process permission replacements.
Expand All @@ -76,6 +82,8 @@ public class CapabilityReplacementsService {
public void processReplacements(CapabilityReplacements capabilityReplacements) {
log.info("Processing assignments of replacement capabilities.");
assignReplacementCapabilities(capabilityReplacements);
log.info("Processing replacement of loadable capabilities.");
replaceLoadable(capabilityReplacements);
log.info("Processing unassigning of replaced capabilities.");
unassignReplacedCapabilities(capabilityReplacements);
log.info("Finished processing capability replacements.");
Expand Down Expand Up @@ -160,7 +168,7 @@ protected Optional<String> permissionNameToCapabilityName(String permissionName)

protected void assignReplacementCapabilities(CapabilityReplacements capabilityReplacements) {
capabilityReplacements.oldCapabilitiesToNewCapabilities().forEach((oldCapabilityName, replacements) -> {
if (replacements != null && !replacements.isEmpty()) {
if (isNotEmpty(replacements)) {
var replacementCapabilities = capabilityService.findByNames(replacements);
var replacementCapabilitySets = capabilitySetService.findByNames(replacements);
assignReplacementsToRoles(capabilityReplacements.oldCapabilityRoleAssignments().get(oldCapabilityName),
Expand All @@ -175,6 +183,95 @@ protected void assignReplacementCapabilities(CapabilityReplacements capabilityRe
});
}

protected void replaceLoadable(CapabilityReplacements capabilityReplacements) {
if (!capabilityReplacements.oldCapabilitiesToNewCapabilities().isEmpty()) {
var oldCapabilities =
capabilityService.findByNames(capabilityReplacements.oldCapabilitiesToNewCapabilities().keySet()).stream()
.collect(toMap(Capability::getName, cap -> cap));
var oldCapabilitySets =
capabilitySetService.findByNames(capabilityReplacements.oldCapabilitiesToNewCapabilities().keySet()).stream()
.collect(toMap(CapabilitySet::getName, capSet -> capSet));

for (var oldCapToNewCaps : capabilityReplacements.oldCapabilitiesToNewCapabilities().entrySet()) {
var oldCapOrCapSetName = oldCapToNewCaps.getKey();
var replacements = oldCapToNewCaps.getValue().stream().toList();
if (isNotEmpty(replacements)) {
log.info("Processing loadable permissions replacements for {}", oldCapOrCapSetName);

var oldCap = oldCapabilities.get(oldCapOrCapSetName);
var oldCapSet = oldCapabilitySets.get(oldCapOrCapSetName);

var replacedLoadablePermissionIds = new HashSet<LoadablePermissionKey>();
if (oldCap != null) {
replacedLoadablePermissionIds.addAll(replaceLoadablePermissionsForCapability(oldCap, replacements));
}
if (oldCapSet != null) {
replacedLoadablePermissionIds.addAll(replaceLoadablePermissionsForCapabilitySet(oldCapSet, replacements));
}
if (!replacedLoadablePermissionIds.isEmpty()) {
loadablePermissionRepository.deleteAllById(replacedLoadablePermissionIds);
}
}
}
}
}

protected Set<LoadablePermissionKey> replaceLoadablePermissionsForCapability(Capability oldCap,
List<String> replacements) {
var replacedLoadablePermissionIds = new HashSet<LoadablePermissionKey>();
var loadablePermissions = loadablePermissionRepository.findAllByCapabilityId(oldCap.getId()).toList();
if (isNotEmpty(loadablePermissions)) {
for (var loadablePermission : loadablePermissions) {
for (var replacement : replacements) {
capabilityService.findByName(replacement).ifPresent(capability -> {
var newLoadablePermission = new LoadablePermissionEntity();
newLoadablePermission.setCapabilityId(capability.getId());
newLoadablePermission.setPermissionName(capability.getPermission());
newLoadablePermission.setRoleId(loadablePermission.getRoleId());
newLoadablePermission.setRole(loadablePermission.getRole());

log.info("Storing capability replacement {} for loadable permission {} of role {}",
capability.getPermission(), loadablePermission.getPermissionName(), loadablePermission.getRoleId());

loadablePermissionRepository.save(newLoadablePermission);
});
}
log.info("Removing replaced loadable permission {} of role {}", loadablePermission.getPermissionName(),
loadablePermission.getRoleId());
replacedLoadablePermissionIds.add(loadablePermission.getId());
}
}
return replacedLoadablePermissionIds;
}

protected HashSet<LoadablePermissionKey> replaceLoadablePermissionsForCapabilitySet(CapabilitySet oldCapSet,
List<String> replacements) {
var replacedLoadablePermissionIds = new HashSet<LoadablePermissionKey>();
var loadablePermissions = loadablePermissionRepository.findAllByCapabilitySetId(oldCapSet.getId()).toList();
if (isNotEmpty(loadablePermissions)) {
for (var loadablePermission : loadablePermissions) {
for (var replacement : replacements) {
capabilitySetService.findByName(replacement).ifPresent(capabilitySet -> {
var newLoadablePermission = new LoadablePermissionEntity();
newLoadablePermission.setCapabilitySetId(capabilitySet.getId());
newLoadablePermission.setPermissionName(capabilitySet.getPermission());
newLoadablePermission.setRoleId(loadablePermission.getRoleId());
newLoadablePermission.setRole(loadablePermission.getRole());

log.info("Storing capability set replacement {} for loadable permission {} of role {}",
capabilitySet.getPermission(), loadablePermission.getPermissionName(), loadablePermission.getRoleId());

loadablePermissionRepository.save(newLoadablePermission);
});
}
log.info("Removing replaced loadable permission {} of role {}", loadablePermission.getPermissionName(),
loadablePermission.getRoleId());
replacedLoadablePermissionIds.add(loadablePermission.getId());
}
}
return replacedLoadablePermissionIds;
}

protected void assignReplacementsToRoles(Set<UUID> roleIds, List<Capability> replacementCapabilities,
List<CapabilitySet> replacementCapabilitySets) {
if (roleIds != null && !roleIds.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -155,6 +156,18 @@ public List<Capability> findByNames(Collection<String> capabilityNames) {
return capabilityEntityMapper.convert(capabilityEntities);
}

/**
* Retrieves capability by capability name.
*
* @param capabilityName - capability name
* @return found {@link Capability} object
*/
@Transactional(readOnly = true)
public Optional<Capability> findByName(String capabilityName) {
var capabilityEntity = capabilityRepository.findByName(capabilityName);
return capabilityEntity.map(capabilityEntityMapper::convert);
}

/**
* Retrieves capabilities by permission names.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Stream;
import org.folio.common.utils.permission.model.PermissionData;
import org.folio.roles.domain.dto.Capability;
import org.folio.roles.domain.dto.CapabilitySet;
import org.folio.roles.domain.dto.Endpoint;
import org.folio.roles.domain.entity.LoadablePermissionEntity;
import org.folio.roles.domain.entity.RoleCapabilityEntity;
import org.folio.roles.domain.entity.RoleCapabilitySetEntity;
import org.folio.roles.domain.entity.UserCapabilityEntity;
import org.folio.roles.domain.entity.UserCapabilitySetEntity;
import org.folio.roles.domain.entity.key.LoadablePermissionKey;
import org.folio.roles.domain.model.CapabilityReplacements;
import org.folio.roles.domain.model.ExtendedCapabilitySet;
import org.folio.roles.domain.model.event.CapabilitySetEvent;
Expand All @@ -33,11 +37,13 @@
import org.folio.roles.integration.kafka.model.CapabilityEvent;
import org.folio.roles.integration.kafka.model.FolioResource;
import org.folio.roles.integration.kafka.model.Permission;
import org.folio.roles.repository.LoadablePermissionRepository;
import org.folio.roles.repository.RoleCapabilityRepository;
import org.folio.roles.repository.RoleCapabilitySetRepository;
import org.folio.roles.repository.UserCapabilityRepository;
import org.folio.roles.repository.UserCapabilitySetRepository;
import org.folio.roles.service.permission.PermissionOverrider;
import org.folio.roles.support.LoadablePermissionUtils;
import org.folio.spring.FolioExecutionContext;
import org.folio.test.types.UnitTest;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -66,6 +72,7 @@ class CapabilityReplacementsServiceTest {
@Mock private CapabilityService capabilityService;
@Mock private CapabilitySetService capabilitySetService;
@Mock private CapabilitySetMapper capabilitySetMapper;
@Mock private LoadablePermissionRepository loadablePermissionRepository;

@Test
void testDeduceReplacementsPositive() {
Expand Down Expand Up @@ -147,6 +154,7 @@ void testProcessReplacements() {
var cap2Id = randomUUID();
var capSet1Id = randomUUID();
var capSet2Id = randomUUID();
var roleId = randomUUID();

when(capabilityService.findByNames(Set.of("newcap1.view", "newcapset2.view"))).thenReturn(
List.of(createCapability(cap1Id, "newcap1.view")));
Expand All @@ -161,6 +169,20 @@ void testProcessReplacements() {
when(capabilitySetService.findByNames(Set.of("oldcap1.view", "oldcapset2.view"))).thenReturn(
List.of(createCapabilitySet(capSet1Id, "oldcapset2.view")));

when(loadablePermissionRepository.findAllByCapabilityId(cap1Id)).thenReturn(Stream.of(
LoadablePermissionUtils.loadablePermissionEntity(roleId,
LoadablePermissionUtils.loadablePermission(roleId, "oldperm1"))));
when(capabilityService.findByName("newcap1.view")).thenReturn(
Optional.of(createCapability(cap1Id, "newcap1.view")));
when(capabilityService.findByName("newcapset2.view")).thenReturn(Optional.empty());

when(loadablePermissionRepository.findAllByCapabilitySetId(capSet1Id)).thenReturn(Stream.of(
LoadablePermissionUtils.loadablePermissionEntity(roleId,
LoadablePermissionUtils.loadablePermission(roleId, "oldset1"))));
when(capabilitySetService.findByName("newcapset1.view")).thenReturn(
Optional.of(createCapabilitySet(capSet1Id, "newcapset1.view")));
when(capabilitySetService.findByName("newcap2.view")).thenReturn(Optional.empty());

var publishedEvents = new ArrayList<>();
doAnswer(inv -> publishedEvents.add(inv.getArgument(0))).when(applicationEventPublisher)
.publishEvent(any(DomainEvent.class));
Expand All @@ -186,6 +208,19 @@ void testProcessReplacements() {
var capabilityReplacements =
new CapabilityReplacements(oldCapabilitiesToNewCapabilities, oldCapabilityRoleAssignments,
oldCapabilityUserAssignments, oldCapabilitySetRoleAssignments, oldCapabilitySetUserAssignments);

var newLoadablePermissions = new ArrayList<LoadablePermissionEntity>();
when(loadablePermissionRepository.save(any())).then(inv -> {
var loadablePermissionEntity = inv.getArgument(0, LoadablePermissionEntity.class);
newLoadablePermissions.add(loadablePermissionEntity);
return loadablePermissionEntity;
});
var deletedIds = new ArrayList<LoadablePermissionKey>();
doAnswer(inv -> {
deletedIds.addAll(inv.getArgument(0));
return null;
}).when(loadablePermissionRepository).deleteAllById(any());

unit.processReplacements(capabilityReplacements);

verify(roleCapabilityService).create(role1Id, List.of(cap1Id), true);
Expand All @@ -209,19 +244,35 @@ void testProcessReplacements() {
assertThat(capSetEvent.getOldObject().getName()).isEqualTo("capset1.view");
assertThat(capSetEvent.getOldObject().getId()).isEqualTo(capSet1Id);
assertThat(capSetEvent.getType()).isEqualTo(DELETE);

assertThat(newLoadablePermissions).hasSize(2);
newLoadablePermissions.forEach(
loadablePermissionEntity -> assertThat(loadablePermissionEntity.getRoleId()).isEqualTo(roleId));
assertThat(newLoadablePermissions.stream()
.filter(lp -> cap1Id.equals(lp.getCapabilityId()) && lp.getPermissionName().equals("newcap1.view"))).hasSize(1);
assertThat(newLoadablePermissions.stream().filter(
lp -> capSet1Id.equals(lp.getCapabilitySetId()) && lp.getPermissionName().equals("newcapset1.view"))).hasSize(1);

assertThat(deletedIds).hasSize(2);
assertThat(deletedIds.stream()
.filter(id -> roleId.equals(id.getRoleId()) && id.getPermissionName().equals("oldperm1"))).hasSize(1);
assertThat(deletedIds.stream().filter(
id -> roleId.equals(id.getRoleId()) && id.getPermissionName().equals("oldset1"))).hasSize(1);
}

private Capability createCapability(UUID id, String name) {
var result = new Capability();
result.setId(id);
result.setName(name);
result.setPermission(name);
return result;
}

private CapabilitySet createCapabilitySet(UUID id, String name) {
var result = new CapabilitySet();
result.setId(id);
result.setName(name);
result.setPermission(name);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public static LoadablePermission loadablePermission(UUID roleId) {
.create();
}

public static LoadablePermission loadablePermission(UUID roleId, String permissionName) {
return Instancio.of(LOADABLE_PERMISSION_MODEL)
.set(field(LoadablePermission::getRoleId), roleId)
.set(field(LoadablePermission::getPermissionName), permissionName)
.create();
}

public static List<LoadablePermission> loadablePermissions(int maxSize) {
return loadablePermissions(1, maxSize);
}
Expand Down

0 comments on commit f5d0244

Please sign in to comment.