Skip to content

Commit

Permalink
MODBULKOPS-314 - Central tenant edit permissions handling (#266)
Browse files Browse the repository at this point in the history
* MODBULKOPS-324 - self check endpoint

* MODBULKOPS-324 - self check endpoint

* MODBULKOPS-314 - verify permissions in updaters

* MODBULKOPS-314 - update tests with permissions validation logic

* MODBULKOPS-314 - update tests with permissions validation logic

* MODBULKOPS-314 - update tests for identifiers resolver

* MODBULKOPS-314 - update tests for identifiers resolver

* MODBULKOPS-314 - update user permissions retrieving
  • Loading branch information
alekGbuz authored Sep 30, 2024
1 parent 37dc6ff commit 2cfe28c
Show file tree
Hide file tree
Showing 22 changed files with 423 additions and 3 deletions.
24 changes: 21 additions & 3 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"provides": [
{
"id": "bulk-operations",
"version": "1.2",
"version": "1.3",
"handlers": [
{
"methods": [ "POST" ],
Expand Down Expand Up @@ -180,6 +180,7 @@
"converter-storage.actionprofile.post",
"converter-storage.mappingprofile.post",
"source-storage.sourceRecords.get",
"bulk-operations.permissions-self-check.get",
"data-import.splitconfig.get",
"data-import.uploadUrl.get",
"data-import.assembleStorageFile.post"
Expand Down Expand Up @@ -261,6 +262,12 @@
"pathPattern": "/bulk-operations/used-tenants/{operationId}",
"permissionsRequired": ["bulk-operations.used.tenants.get"],
"modulePermissions": [ ]
},
{
"methods": [ "GET" ],
"pathPattern": "/bulk-operations/permissions-self-check",
"permissionsRequired": ["bulk-operations.permissions-self-check.get"],
"modulePermissions": ["perms.users.get"]
}
]
},
Expand Down Expand Up @@ -403,6 +410,11 @@
"displayName" : "bulk edit users write permissions",
"description" : "Bulk edit users write permissions"
},
{
"permissionName" : "bulk-operations.permissions-self-check.get",
"displayName" : "Set of users permissions for self check",
"description" : "Set of users permissions for self check"
},
{
"permissionName" : "bulk-operations.all",
"displayName" : "bulk-operations all",
Expand All @@ -423,7 +435,8 @@
"bulk-operations.files.item.delete",
"bulk-operations.item.cancel.post",
"bulk-operations.item.query.post",
"bulk-operations.used.tenants.get"
"bulk-operations.used.tenants.get",
"bulk-operations.permissions-self-check.get"
]
}
],
Expand Down Expand Up @@ -498,7 +511,7 @@
},
{
"id": "bulk-edit",
"version": "4.0"
"version": "4.1"
},
{
"id": "data-export-spring",
Expand Down Expand Up @@ -541,6 +554,11 @@
"version": "2.0"
},
{
"id": "permissions",
"version": "5.6"
},
{

"id": "data-import-converter-storage",
"version": "1.4"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.folio.bulkops.client;

import java.util.List;

import org.folio.bulkops.configs.FeignClientConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "bulk-operations", configuration = FeignClientConfiguration.class)
public interface PermissionsSelfCheckClient {

@GetMapping(value = "/permissions-self-check", produces = MediaType.APPLICATION_JSON_VALUE)
List<String> getUserPermissionsForSelfCheck();
}
15 changes: 15 additions & 0 deletions src/main/java/org/folio/bulkops/client/UserPermissionsClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.folio.bulkops.client;

import org.folio.bulkops.configs.FeignClientConfiguration;
import org.folio.bulkops.domain.bean.UserPermissions;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "perms/users", configuration = FeignClientConfiguration.class)
public interface UserPermissionsClient {

@GetMapping(value = "/{userId}/permissions?expanded=true&indexField=userId", produces = MediaType.APPLICATION_JSON_VALUE)
UserPermissions getPermissions(@PathVariable String userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.apache.commons.lang3.ArrayUtils;
import org.codehaus.plexus.util.FileUtils;
import org.folio.bulkops.client.RemoteFileSystemClient;
import org.folio.bulkops.client.UserPermissionsClient;
import org.folio.bulkops.domain.dto.BulkOperationCollection;
import org.folio.bulkops.domain.dto.BulkOperationDto;
import org.folio.bulkops.domain.dto.BulkOperationRuleCollection;
Expand All @@ -42,6 +43,7 @@
import org.folio.bulkops.service.LogFilesService;
import org.folio.bulkops.service.PreviewService;
import org.folio.bulkops.service.RuleService;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.cql.JpaCqlRepository;
import org.folio.spring.data.OffsetRequest;
import org.springframework.core.io.ByteArrayResource;
Expand Down Expand Up @@ -76,6 +78,8 @@ public class BulkOperationController implements BulkOperationsApi {
private final ListUsersService listUsersService;
private final NoteProcessorFactory noteProcessorFactory;
private final BulkOperationRepository bulkOperationRepository;
private final UserPermissionsClient userPermissionsClient;
private final FolioExecutionContext folioExecutionContext;

@Override
public ResponseEntity<BulkOperationCollection> getBulkOperationCollection(String query, Integer offset, Integer limit) {
Expand Down Expand Up @@ -227,4 +231,10 @@ public ResponseEntity<List<String>> getListUsedTenants(UUID operationId) {
public ResponseEntity<BulkOperationDto> triggerBulkEditByQuery(UUID xOkapiUserId, QueryRequest queryRequest) {
return new ResponseEntity<>(bulkOperationMapper.mapToDto(bulkOperationService.triggerByQuery(xOkapiUserId, queryRequest)), HttpStatus.OK);
}

@Override
public ResponseEntity<List<String>> getUsersPermissions() {
var permissions = userPermissionsClient.getPermissions(folioExecutionContext.getUserId().toString());
return new ResponseEntity<>(permissions.getPermissionNames(), HttpStatus.OK);
}
}
23 changes: 23 additions & 0 deletions src/main/java/org/folio/bulkops/domain/bean/UserPermissions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.folio.bulkops.domain.bean;

import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.With;

@Data
@With
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public class UserPermissions {

@JsonProperty("permissionNames")
private List<String> permissionNames = new ArrayList<>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.folio.bulkops.exception;

public class WritePermissionDoesNotExist extends RuntimeException {
public WritePermissionDoesNotExist(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
import org.folio.bulkops.domain.bean.Item;
import org.folio.bulkops.domain.bean.ItemCollection;
import org.folio.bulkops.domain.dto.BulkOperationRule;
import org.folio.bulkops.domain.dto.EntityType;
import org.folio.bulkops.domain.entity.BulkOperation;
import org.folio.bulkops.processor.permissions.check.PermissionsValidator;
import org.folio.bulkops.service.ConsortiaService;
import org.folio.bulkops.service.ErrorService;
import org.folio.bulkops.service.HoldingsReferenceService;
Expand All @@ -49,6 +51,7 @@
public class FolioInstanceUpdateProcessor extends AbstractUpdateProcessor<ExtendedInstance> {
private static final String ERROR_MESSAGE_TEMPLATE = "No change in value for instance required, %s associated records have been updated.";
private static final String ERROR_NO_AFFILIATION_TO_EDIT_HOLDINGS = "User %s does not have required affiliation to edit the holdings record - %s on the tenant %s";
private static final String NO_INSTANCE_WRITE_PERMISSIONS_TEMPLATE = "User %s does not have required permission to edit the instance record - %s=%s on the tenant ";

private final InstanceClient instanceClient;
private final UserClient userClient;
Expand All @@ -61,9 +64,12 @@ public class FolioInstanceUpdateProcessor extends AbstractUpdateProcessor<Extend
private final ConsortiaService consortiaService;
private final FolioModuleMetadata folioModuleMetadata;
private final FolioExecutionContext folioExecutionContext;
private final PermissionsValidator permissionsValidator;

@Override
public void updateRecord(ExtendedInstance extendedInstance) {
permissionsValidator.checkIfBulkEditWritePermissionExists(extendedInstance.getTenantId(), EntityType.INSTANCE,
NO_INSTANCE_WRITE_PERMISSIONS_TEMPLATE + extendedInstance.getTenantId());
var instance = extendedInstance.getEntity();
instanceClient.updateInstance(instance.withIsbn(null).withIssn(null), instance.getId());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
import org.folio.bulkops.domain.bean.HoldingsRecord;
import org.folio.bulkops.domain.bean.Item;
import org.folio.bulkops.domain.dto.BulkOperationRule;
import org.folio.bulkops.domain.dto.EntityType;
import org.folio.bulkops.domain.entity.BulkOperation;
import org.folio.bulkops.processor.permissions.check.PermissionsValidator;
import org.folio.bulkops.service.ConsortiaService;
import org.folio.bulkops.service.ErrorService;
import org.folio.bulkops.service.RuleService;
Expand All @@ -35,6 +37,7 @@
@RequiredArgsConstructor
public class HoldingsUpdateProcessor extends AbstractUpdateProcessor<ExtendedHoldingsRecord> {
private static final String ERROR_MESSAGE_TEMPLATE = "No change in value for holdings record required, associated %s item(s) have been updated.";
private static final String NO_HOLDING_WRITE_PERMISSIONS_TEMPLATE = "User %s does not have required permission to edit the holdings record - %s=%s on the tenant ";

private final HoldingsClient holdingsClient;
private final ItemClient itemClient;
Expand All @@ -43,19 +46,24 @@ public class HoldingsUpdateProcessor extends AbstractUpdateProcessor<ExtendedHol
private final FolioModuleMetadata folioModuleMetadata;
private final FolioExecutionContext folioExecutionContext;
private final ConsortiaService consortiaService;
private final PermissionsValidator permissionsValidator;

@Override
public void updateRecord(ExtendedHoldingsRecord extendedHoldingsRecord) {
var holdingsRecord = extendedHoldingsRecord.getEntity();
if (consortiaService.isCurrentTenantCentralTenant(folioExecutionContext.getTenantId())) {
var tenantId = extendedHoldingsRecord.getTenantId();
permissionsValidator.checkIfBulkEditWritePermissionExists(tenantId, EntityType.HOLDINGS_RECORD,
NO_HOLDING_WRITE_PERMISSIONS_TEMPLATE + tenantId);
try (var ignored = new FolioExecutionContextSetter(prepareContextForTenant(tenantId, folioModuleMetadata, folioExecutionContext))) {
holdingsClient.updateHoldingsRecord(
holdingsRecord.withInstanceHrid(null).withItemBarcode(null).withInstanceTitle(null),
holdingsRecord.getId()
);
}
} else {
permissionsValidator.checkIfBulkEditWritePermissionExists(folioExecutionContext.getTenantId(), EntityType.HOLDINGS_RECORD,
NO_HOLDING_WRITE_PERMISSIONS_TEMPLATE + folioExecutionContext.getTenantId());
holdingsClient.updateHoldingsRecord(
holdingsRecord.withInstanceHrid(null).withItemBarcode(null).withInstanceTitle(null),
holdingsRecord.getId()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.folio.bulkops.client.ItemClient;
import org.folio.bulkops.domain.bean.ExtendedItem;
import org.folio.bulkops.domain.dto.EntityType;
import org.folio.bulkops.processor.permissions.check.PermissionsValidator;
import org.folio.bulkops.service.ConsortiaService;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.FolioModuleMetadata;
Expand All @@ -16,20 +18,27 @@
@RequiredArgsConstructor
public class ItemUpdateProcessor extends AbstractUpdateProcessor<ExtendedItem> {

private static final String NO_ITEM_WRITE_PERMISSIONS_TEMPLATE = "User %s does not have required permission to edit the item record - %s=%s on the tenant ";

private final ItemClient itemClient;
private final FolioModuleMetadata folioModuleMetadata;
private final FolioExecutionContext folioExecutionContext;
private final ConsortiaService consortiaService;
private final PermissionsValidator permissionsValidator;

@Override
public void updateRecord(ExtendedItem extendedItem) {
var item = extendedItem.getEntity();
if (consortiaService.isCurrentTenantCentralTenant(folioExecutionContext.getTenantId())) {
var tenantId = extendedItem.getTenantId();
permissionsValidator.checkIfBulkEditWritePermissionExists(tenantId, EntityType.ITEM,
NO_ITEM_WRITE_PERMISSIONS_TEMPLATE + tenantId);
try (var ignored = new FolioExecutionContextSetter(prepareContextForTenant(tenantId, folioModuleMetadata, folioExecutionContext))) {
itemClient.updateItem(item.withHoldingsData(null), item.getId());
}
} else {
permissionsValidator.checkIfBulkEditWritePermissionExists(folioExecutionContext.getTenantId(), EntityType.ITEM,
NO_ITEM_WRITE_PERMISSIONS_TEMPLATE + folioExecutionContext.getTenantId());
itemClient.updateItem(item.withHoldingsData(null), item.getId());
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/folio/bulkops/processor/UserUpdateProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@

import org.folio.bulkops.client.UserClient;
import org.folio.bulkops.domain.bean.User;
import org.folio.bulkops.domain.dto.EntityType;
import org.folio.bulkops.processor.permissions.check.PermissionsValidator;
import org.folio.spring.FolioExecutionContext;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class UserUpdateProcessor extends AbstractUpdateProcessor<User> {

private static final String NO_USER_WRITE_PERMISSIONS_TEMPLATE = "User %s does not have required permission to edit the user record - %s=%s on the tenant ";

private final UserClient userClient;
private final PermissionsValidator permissionsValidator;
private final FolioExecutionContext folioExecutionContext;

@Override
public void updateRecord(User user) {
permissionsValidator.checkIfBulkEditWritePermissionExists(folioExecutionContext.getTenantId(), EntityType.ITEM,
NO_USER_WRITE_PERMISSIONS_TEMPLATE + folioExecutionContext.getTenantId());
userClient.updateUser(user, user.getId());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.folio.bulkops.processor.permissions.check;

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

import org.folio.bulkops.client.PermissionsSelfCheckClient;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.FolioModuleMetadata;
import org.folio.spring.scope.FolioExecutionContextSetter;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;

import static org.folio.bulkops.util.FolioExecutionContextUtil.prepareContextForTenant;

@Component
@RequiredArgsConstructor
public class PermissionsProvider {

private final PermissionsSelfCheckClient permissionsSelfCheckClient;
private final FolioExecutionContext folioExecutionContext;
private final FolioModuleMetadata folioModuleMetadata;

@Cacheable(cacheNames = "userPermissions")
public List<String> getUserPermissions(String tenantId, UUID userId) {
try (var ignored = new FolioExecutionContextSetter(prepareContextForTenant(tenantId, folioModuleMetadata, folioExecutionContext))) {
return permissionsSelfCheckClient.getUserPermissionsForSelfCheck();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.folio.bulkops.processor.permissions.check;

import org.folio.bulkops.domain.dto.EntityType;
import org.folio.bulkops.exception.WritePermissionDoesNotExist;
import org.folio.spring.FolioExecutionContext;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

@Component
@RequiredArgsConstructor
@Log4j2
public class PermissionsValidator {

public static final String BULK_EDIT_INVENTORY_WRITE_PERMISSION = "bulk-operations.item.inventory.put";
public static final String BULK_EDIT_USERS_WRITE_PERMISSION = "bulk-operations.item.users.put";

private final PermissionsProvider permissionsProvider;
private final RequiredPermissionResolver requiredPermissionResolver;
private final FolioExecutionContext folioExecutionContext;

public void checkIfBulkEditWritePermissionExists(String tenantId, EntityType entityType, String errorMessage) {
if (!isBulkEditWritePermissionExists(tenantId, entityType)) {
throw new WritePermissionDoesNotExist(errorMessage);
}
}

private boolean isBulkEditWritePermissionExists(String tenantId, EntityType entityType) {
var readPermissionForEntity = requiredPermissionResolver.getWritePermission(entityType);
var userPermissions = permissionsProvider.getUserPermissions(tenantId, folioExecutionContext.getUserId());
var isWritePermissionsExist = false;
if (entityType == EntityType.USER) {
isWritePermissionsExist = userPermissions.contains(readPermissionForEntity) && userPermissions.contains(BULK_EDIT_USERS_WRITE_PERMISSION);
} else {
isWritePermissionsExist = userPermissions.contains(readPermissionForEntity) && userPermissions.contains(BULK_EDIT_INVENTORY_WRITE_PERMISSION);
}
log.info("isBulkEditWritePermissionExists:: user {} has write permissions {} for {} in tenant {}", folioExecutionContext.getUserId(),
isWritePermissionsExist, entityType, tenantId);
return isWritePermissionsExist;
}

}
Loading

0 comments on commit 2cfe28c

Please sign in to comment.