RED-7700: Safe rule execution

This commit is contained in:
Maverick Studer 2024-03-08 15:06:14 +01:00
parent dff30d5f7d
commit d845b4cef2
15 changed files with 177 additions and 200 deletions

View File

@ -6,7 +6,7 @@ plugins {
jacoco
}
val redactionServiceVersion by rootProject.extra { "4.246.0" }
val redactionServiceVersion by rootProject.extra { "4.262.0" }
val pdftronRedactionServiceVersion by rootProject.extra { "4.48.0" }
val redactionReportServiceVersion by rootProject.extra { "4.47.0" }
val searchServiceVersion by rootProject.extra { "2.71.0" }

View File

@ -7,7 +7,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
@ -27,17 +26,15 @@ import com.iqser.red.service.persistence.management.v1.processor.exception.FileU
import com.iqser.red.service.persistence.management.v1.processor.service.RulesValidationService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.RulesValidationMapper;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RulesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsSyntaxValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RuleSyntaxErrorMessage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RuleSyntaxWarningMessage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequestModel;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import feign.FeignException;
@ -58,45 +55,26 @@ public class RulesController implements RulesResource {
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<DroolsSyntaxValidationResponse> upload(@RequestBody RulesUploadRequestModel rules) {
public ResponseEntity<DroolsValidationResponse> upload(@RequestBody RulesUploadRequestModel rules) {
RulesUploadRequest rulesUploadRequest = RulesUploadRequest.fromModel(rules);
DroolsSyntaxValidationResponse droolsSyntaxValidationResponse = DroolsSyntaxValidationResponse.builder().build();
try {
DroolsSyntaxValidation droolsSyntaxValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules());
var rulesSyntaxWarningMessages = droolsSyntaxValidation.getDroolsSyntaxDeprecatedWarnings()
.stream()
.map(warningMessage -> RuleSyntaxWarningMessage.builder()
.line(warningMessage.getLine())
.column(warningMessage.getColumn())
.message(warningMessage.getMessage())
.build())
.collect(Collectors.toList());
droolsSyntaxValidationResponse.setRulesSyntaxWarningMessages(rulesSyntaxWarningMessages);
if (!droolsSyntaxValidation.isCompiled()) {
var rulesSyntaxErrorMessages = droolsSyntaxValidation.getDroolsSyntaxErrorMessages()
.stream()
.map(errorMessage -> RuleSyntaxErrorMessage.builder()
.line(errorMessage.getLine())
.column(errorMessage.getColumn())
.message(errorMessage.getMessage())
.build())
.toList();
droolsSyntaxValidationResponse.setRulesSyntaxErrorMessages(rulesSyntaxErrorMessages);
if (!rules.isDryRun()) {
return new ResponseEntity<>(droolsSyntaxValidationResponse, HttpStatus.BAD_REQUEST);
}
}
DroolsValidationResponse droolsValidationResponse = new DroolsValidationResponse();
if (rules.isDryRun()) {
return ResponseEntity.ok(droolsSyntaxValidationResponse);
try {
var droolsValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules());
droolsValidationResponse = RulesValidationMapper.createFromDroolsValidation(droolsValidation);
if (!droolsValidation.isCompiled()) {
return new ResponseEntity<>(droolsValidationResponse, !rules.isDryRun() ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK);
}
} catch (FeignException e) {
if (e.status() == HttpStatus.BAD_REQUEST.value()) {
throw new BadRequestException("The provided rule string is not a valid drools rule file!");
}
}
rulesPersistenceService.setRules(rulesUploadRequest.getRules(), rulesUploadRequest.getDossierTemplateId(), rulesUploadRequest.getRuleFileType());
if (!rules.isDryRun()) {
rulesPersistenceService.setRules(rulesUploadRequest.getRules(), rulesUploadRequest.getDossierTemplateId(), rulesUploadRequest.getRuleFileType());
}
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
@ -105,7 +83,7 @@ public class RulesController implements RulesResource {
.message(String.format("%s Rules have been updated", rulesUploadRequest.getRuleFileType()))
.build());
return ResponseEntity.ok(droolsSyntaxValidationResponse);
return new ResponseEntity<>(droolsValidationResponse, HttpStatus.OK);
}
@ -129,9 +107,9 @@ public class RulesController implements RulesResource {
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<DroolsSyntaxValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@RequestPart(name = "file") MultipartFile file) {
public ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@RequestPart(name = "file") MultipartFile file) {
return uploadFile(dossierTemplateId, RuleFileType.ENTITY, dryRun, file);
}
@ -139,10 +117,10 @@ public class RulesController implements RulesResource {
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<DroolsSyntaxValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@RequestPart(name = "file") MultipartFile file) {
public ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@RequestPart(name = "file") MultipartFile file) {
try {
return upload(new RulesUploadRequestModel(new String(file.getBytes(), StandardCharsets.UTF_8), dossierTemplateId, ruleFileType, dryRun));

View File

@ -29,18 +29,17 @@ import com.iqser.red.service.persistence.management.v1.processor.exception.BadRe
import com.iqser.red.service.persistence.management.v1.processor.service.RulesValidationService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.RulesValidationMapper;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileAttributeDefinition;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileAttributeDefinitionList;
import com.iqser.red.service.persistence.service.v2.api.external.model.RulesValidationMessage;
import com.iqser.red.service.persistence.service.v2.api.external.model.RulesValidationResponse;
import com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import feign.FeignException;
@ -77,9 +76,9 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource {
@PreAuthorize("hasAuthority('" + WRITE_RULES + "') and hasAuthority('" + EXPERIMENTAL + "')")
public ResponseEntity<RulesValidationResponse> uploadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun) {
public ResponseEntity<DroolsValidationResponse> uploadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun) {
// Try to get dossier template to return 404 if it does not exist
getDossierTemplate(dossierTemplateId);
@ -99,9 +98,9 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource {
@PreAuthorize("hasAuthority('" + WRITE_RULES + "') and hasAuthority('" + EXPERIMENTAL + "')")
public ResponseEntity<RulesValidationResponse> uploadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun) {
public ResponseEntity<DroolsValidationResponse> uploadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun) {
// Try to get dossier template to return 404 if it does not exist
getDossierTemplate(dossierTemplateId);
@ -156,27 +155,21 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource {
@SneakyThrows
private ResponseEntity<RulesValidationResponse> uploadRules(String dossierTemplateId, RuleFileType ruleFileType, MultipartFile file, boolean dryRun) {
private ResponseEntity<DroolsValidationResponse> uploadRules(String dossierTemplateId, RuleFileType ruleFileType, MultipartFile file, boolean dryRun) {
var rulesUploadRequest = RulesUploadRequest.builder()
.rules(new String(file.getBytes(), StandardCharsets.UTF_8))
.dossierTemplateId(dossierTemplateId)
.ruleFileType(ruleFileType)
.build();
try {
DroolsSyntaxValidation droolsSyntaxValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules());
if (!droolsSyntaxValidation.isCompiled()) {
var rulesSyntaxErrorMessages = droolsSyntaxValidation.getDroolsSyntaxErrorMessages()
.stream()
.map(errorMessage -> RulesValidationMessage.builder()
.line(errorMessage.getLine())
.column(errorMessage.getColumn())
.message(errorMessage.getMessage())
.build())
.toList();
DroolsValidationResponse rulesValidationResponse = new DroolsValidationResponse();
// TODO Add warning and deprecations to response
return new ResponseEntity<>(RulesValidationResponse.builder().errors(rulesSyntaxErrorMessages).build(), HttpStatus.UNPROCESSABLE_ENTITY);
try {
var droolsValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules());
rulesValidationResponse = RulesValidationMapper.createFromDroolsValidation(droolsValidation);
if (!droolsValidation.isCompiled()) {
return new ResponseEntity<>(rulesValidationResponse, !dryRun ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK);
}
} catch (FeignException e) {
if (e.status() == HttpStatus.BAD_REQUEST.value()) {
@ -195,8 +188,7 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource {
.message(String.format("%s rules have been %s", rulesUploadRequest.getRuleFileType(), dryRun ? "validated" : "updated"))
.build());
// TODO Add warning and deprecations to response
return new ResponseEntity<>(RulesValidationResponse.builder().build(), HttpStatus.OK);
return new ResponseEntity<>(rulesValidationResponse, HttpStatus.OK);
}

View File

@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsSyntaxValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequestModel;
@ -46,8 +46,8 @@ public interface RulesResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = RULES_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")})
ResponseEntity<DroolsSyntaxValidationResponse> upload(@RequestBody RulesUploadRequestModel rules);
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be compiled.")})
ResponseEntity<DroolsValidationResponse> upload(@RequestBody RulesUploadRequestModel rules);
@ResponseBody
@ -74,20 +74,20 @@ public interface RulesResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + UPLOAD_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")})
ResponseEntity<DroolsSyntaxValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file);
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be compiled.")})
ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = RULES_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + RULE_FILE_TYPE_PATH_VARIABLE + UPLOAD_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done"), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")})
ResponseEntity<DroolsSyntaxValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file);
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful or rules validation done"), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be compiled.")})
ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType,
@RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file);
@ResponseBody

View File

@ -13,14 +13,14 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Schema(description = "Shows where to find problems in the uploaded rules file")
public class RulesValidationMessage {
@Schema(description = "Show where blacklisted keywords are used in the rules file.")
public class RulesBlacklistMessage {
@Schema(description = "The Line where it occurred.")
@Schema(description = "The Line where the blacklisted keywords are used.")
Integer line;
@Schema(description = "The Column where it occurred.")
@Schema(description = "The Column where the blacklisted keywords are used.")
Integer column;
@Schema(description = "The error or warning message.")
@Schema(description = "The message containing all blacklisted keywords that were used.")
String message;
}

View File

@ -1,28 +0,0 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Schema(description = "Show where deprecated methods are used in the rules file.")
public class RulesDeprecationMessage {
@Schema(description = "The Line where the deprecated method is used.")
Integer line;
@Schema(description = "The Column where the deprecated method is used.")
Integer column;
@Schema(description = "The deprecated message, should point to method that should be used instead.")
String message;
@Schema(description = "The name of the deprecated method.")
String methodName;
}

View File

@ -1,18 +1,15 @@
package com.iqser.red.service.persistence.service.v2.api.external.resource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileAttributeDefinitionList;
import com.iqser.red.service.persistence.service.v2.api.external.model.RulesValidationResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@ -61,8 +58,8 @@ public interface DossierTemplateResource {
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + ENTITY_RULES_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Upload a component or entity rules file in drools format for a specific DossierTemplate.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful."), @ApiResponse(responseCode = "404", description = "The DossierTemplate is not found."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be verified.")})
ResponseEntity<RulesValidationResponse> uploadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful."), @ApiResponse(responseCode = "404", description = "The DossierTemplate is not found."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be compiled.")})
ResponseEntity<DroolsValidationResponse> uploadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun);
@ -80,10 +77,10 @@ public interface DossierTemplateResource {
+ DOSSIER_TEMPLATE_ID_PATH_VARIABLE
+ COMPONENT_RULES_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Upload a component or entity rules file in drools format for a specific DossierTemplate.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful."), @ApiResponse(responseCode = "404", description = "The DossierTemplate is not found."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be verified.")})
ResponseEntity<RulesValidationResponse> uploadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun);
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful."), @ApiResponse(responseCode = "404", description = "The DossierTemplate is not found."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified."), @ApiResponse(responseCode = "422", description = "Uploaded rules could not be compiled.")})
ResponseEntity<DroolsValidationResponse> uploadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun);
@ResponseBody

View File

@ -6,7 +6,6 @@ import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@ -77,7 +76,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.legalbasis.LegalBasis;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.tenantcommons.TenantContext;
@ -274,7 +273,7 @@ public class DossierTemplateImportService {
Function.identity()));
reportTemplateFilenameList = reportTemplateList.stream()
.map(rt -> rt.isMultiFileReport() ? rt.getFileName() + ExportFilename.REPORT_TEMPLATE_MULTI_FILE.getFilename() : rt.getFileName())
.collect(Collectors.toList());
.toList();
} else {
reportTemplateBytesMap.put(ze.getName(), bos);
}
@ -536,19 +535,21 @@ public class DossierTemplateImportService {
private void setRulesWhenCompiled(ImportTemplateResult request, String dossierTemplateId) {
DroolsSyntaxValidation droolsSyntaxValidation = rulesValidationService.validateRules(RuleFileType.ENTITY, request.getRuleSet());
if (!droolsSyntaxValidation.isCompiled()) {
droolsSyntaxValidation.getDroolsSyntaxErrorMessages()
DroolsValidation droolsValidation = rulesValidationService.validateRules(RuleFileType.ENTITY, request.getRuleSet());
if (!droolsValidation.isCompiled()) {
droolsValidation.getSyntaxErrorMessages().forEach(errorMessage -> log.error(errorMessage.getMessage()));
droolsValidation.getBlacklistErrorMessages()
.forEach(errorMessage -> log.error(errorMessage.getMessage()));
throw new BadRequestException("The entity rules do not compile!");
throw new BadRequestException("The entity rules do not compile or contain blacklisted keywords!");
}
rulesPersistenceService.setRules(request.getRuleSet(), dossierTemplateId, RuleFileType.ENTITY);
if (request.getComponentRuleSet() != null) {
DroolsSyntaxValidation componentDroolsSyntaxValidation = rulesValidationService.validateRules(RuleFileType.COMPONENT, request.getComponentRuleSet());
if (!componentDroolsSyntaxValidation.isCompiled()) {
componentDroolsSyntaxValidation.getDroolsSyntaxErrorMessages()
DroolsValidation componentDroolsValidation = rulesValidationService.validateRules(RuleFileType.COMPONENT, request.getComponentRuleSet());
if (!componentDroolsValidation.isCompiled()) {
componentDroolsValidation.getSyntaxErrorMessages().forEach(errorMessage -> log.error(errorMessage.getMessage()));
componentDroolsValidation.getBlacklistErrorMessages()
.forEach(errorMessage -> log.error(errorMessage.getMessage()));
throw new BadRequestException("The component rules do not compile!");
throw new BadRequestException("The component rules do not compile or contain blacklisted keywords!");
}
rulesPersistenceService.setRules(request.getComponentRuleSet(), dossierTemplateId, RuleFileType.COMPONENT);
}

View File

@ -4,7 +4,7 @@ import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionservice.RedactionClient;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
import com.iqser.red.service.redaction.v1.model.RuleValidationModel;
import lombok.RequiredArgsConstructor;
@ -16,7 +16,7 @@ public class RulesValidationService {
private final RedactionClient redactionServiceClient;
public DroolsSyntaxValidation validateRules(RuleFileType ruleFileType, String rules) {
public DroolsValidation validateRules(RuleFileType ruleFileType, String rules) {
return redactionServiceClient.testRules(new RuleValidationModel(ruleFileType.name(), rules));
}

View File

@ -0,0 +1,34 @@
package com.iqser.red.service.persistence.management.v1.processor.utils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RuleBlacklistErrorMessage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RuleSyntaxErrorMessage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RuleSyntaxWarningMessage;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
public class RulesValidationMapper {
public static DroolsValidationResponse createFromDroolsValidation(DroolsValidation droolsValidation) {
return DroolsValidationResponse.builder()
.syntaxErrorMessages(droolsValidation.getSyntaxErrorMessages()
.stream()
.map(droolsSyntaxErrorMessage -> new RuleSyntaxErrorMessage(droolsSyntaxErrorMessage.getLine(),
droolsSyntaxErrorMessage.getColumn(),
droolsSyntaxErrorMessage.getMessage()))
.toList())
.deprecatedWarnings(droolsValidation.getDeprecatedWarnings()
.stream()
.map(droolsSyntaxDeprecatedWarnings -> new RuleSyntaxWarningMessage(droolsSyntaxDeprecatedWarnings.getLine(),
droolsSyntaxDeprecatedWarnings.getColumn(),
droolsSyntaxDeprecatedWarnings.getMessage()))
.toList())
.blacklistErrorMessages(droolsValidation.getBlacklistErrorMessages()
.stream()
.map(droolsBlacklistErrorMessage -> new RuleBlacklistErrorMessage(droolsBlacklistErrorMessage.getLine(),
droolsBlacklistErrorMessage.getColumn(),
droolsBlacklistErrorMessage.getMessage()))
.toList()).build();
}
}

View File

@ -6,6 +6,7 @@ import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
@ -17,11 +18,11 @@ import com.iqser.red.service.peristence.v1.server.integration.client.VersionClie
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsSyntaxValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequestModel;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxDeprecatedWarnings;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxErrorMessage;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
import com.iqser.red.service.redaction.v1.model.RuleValidationModel;
import feign.FeignException;
@ -70,31 +71,33 @@ public class RulesTest extends AbstractPersistenceServerServiceTest {
errorMessages.add(DroolsSyntaxErrorMessage.builder().line(1).column(0).message("error message").build());
var request = new RulesUploadRequestModel("lorem ipsum", dossierTemplate.getId(), RuleFileType.ENTITY, true);
// case 1: dry-run true, no error messages just warning messages
when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsSyntaxValidation.builder()
.droolsSyntaxErrorMessages(Collections.emptyList())
.droolsSyntaxDeprecatedWarnings(
when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsValidation.builder()
.syntaxErrorMessages(Collections.emptyList())
.deprecatedWarnings(
warningMessages)
.build());
ResponseEntity<DroolsSyntaxValidationResponse> response = rulesClient.upload(request);
ResponseEntity<DroolsValidationResponse> response = rulesClient.upload(request);
assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.SC_OK);
assertThat(response.getBody().getRulesSyntaxWarningMessages()).isNotEmpty();
assertThat(response.getBody().getRulesSyntaxErrorMessages()).isEmpty();
Objects.requireNonNull(response.getBody());
assertThat(response.getBody().getSyntaxErrorMessages()).isEmpty();
assertThat(response.getBody().getDeprecatedWarnings()).isNotEmpty();
assertThat(versionClient.getVersions(List.of(dossierTemplate.getId()))
.get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(2); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate()
// case 2: dry-run true, error messages and warning messages
when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsSyntaxValidation.builder()
.droolsSyntaxErrorMessages(errorMessages)
.droolsSyntaxDeprecatedWarnings(
when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsValidation.builder()
.syntaxErrorMessages(errorMessages)
.deprecatedWarnings(
warningMessages)
.build());
response = rulesClient.upload(request);
assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.SC_OK);
assertThat(response.getBody().getRulesSyntaxWarningMessages()).isNotEmpty();
assertThat(response.getBody().getRulesSyntaxErrorMessages()).isNotEmpty();
Objects.requireNonNull(response.getBody());
assertThat(response.getBody().getDeprecatedWarnings()).isNotEmpty();
assertThat(response.getBody().getSyntaxErrorMessages()).isNotEmpty();
assertThat(versionClient.getVersions(List.of(dossierTemplate.getId()))
.get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(2); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate()
@ -113,31 +116,32 @@ public class RulesTest extends AbstractPersistenceServerServiceTest {
errorMessages.add(DroolsSyntaxErrorMessage.builder().line(1).column(0).message("error message").build());
var request = new RulesUploadRequestModel("lorem ipsum", dossierTemplate.getId(), RuleFileType.ENTITY, false);
// case 1: dry-run false, error messages and warning messages
when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsSyntaxValidation.builder()
.droolsSyntaxErrorMessages(errorMessages)
.droolsSyntaxDeprecatedWarnings(
when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsValidation.builder()
.syntaxErrorMessages(errorMessages)
.deprecatedWarnings(
warningMessages)
.build());
try {
rulesClient.upload(request);
} catch (FeignException e) {
assertThat(e.status()).isEqualTo(HttpStatus.SC_BAD_REQUEST);
assertThat(e.status()).isEqualTo(HttpStatus.SC_UNPROCESSABLE_ENTITY);
}
assertThat(versionClient.getVersions(List.of(dossierTemplate.getId()))
.get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(2); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate()
// case 2: dry-run false, no error messages just warning messages
when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsSyntaxValidation.builder()
.droolsSyntaxErrorMessages(Collections.emptyList())
.droolsSyntaxDeprecatedWarnings(
when(redactionClient.testRules(new RuleValidationModel(request.getRuleFileType().name(), request.getRules()))).thenReturn(DroolsValidation.builder()
.syntaxErrorMessages(Collections.emptyList())
.deprecatedWarnings(
warningMessages)
.build());
ResponseEntity<DroolsSyntaxValidationResponse> response = rulesClient.upload(request);
ResponseEntity<DroolsValidationResponse> response = rulesClient.upload(request);
assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.SC_OK);
assertThat(response.getBody().getRulesSyntaxWarningMessages()).isNotEmpty();
assertThat(response.getBody().getRulesSyntaxErrorMessages()).isEmpty();
Objects.requireNonNull(response.getBody());
assertThat(response.getBody().getDeprecatedWarnings()).isNotEmpty();
assertThat(response.getBody().getSyntaxErrorMessages()).isEmpty();
assertThat(versionClient.getVersions(List.of(dossierTemplate.getId()))
.get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(3); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate()

View File

@ -22,7 +22,7 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.ApplicationConfig;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
import com.iqser.red.storage.commons.service.StorageService;
import com.iqser.red.storage.commons.utils.FileSystemBackedStorageService;
import com.knecon.fforesight.databasetenantcommons.providers.TenantCreatedListener;
@ -258,9 +258,9 @@ public abstract class AbstractPersistenceServerServiceTest {
0,
0,
0));
when(redactionClient.testRules(Mockito.any())).thenReturn(DroolsSyntaxValidation.builder()
.droolsSyntaxErrorMessages(Collections.emptyList())
.droolsSyntaxDeprecatedWarnings(Collections.emptyList())
when(redactionClient.testRules(Mockito.any())).thenReturn(DroolsValidation.builder()
.syntaxErrorMessages(Collections.emptyList())
.deprecatedWarnings(Collections.emptyList())
.build());
}

View File

@ -1,25 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules;
import java.util.LinkedList;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class DroolsSyntaxValidationResponse {
@Builder.Default
List<RuleSyntaxErrorMessage> rulesSyntaxErrorMessages = new LinkedList<>();
@Builder.Default
List<RuleSyntaxWarningMessage> rulesSyntaxWarningMessages = new LinkedList<>();
}

View File

@ -1,4 +1,4 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
package com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules;
import java.util.ArrayList;
import java.util.List;
@ -13,22 +13,20 @@ import lombok.experimental.FieldDefaults;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Schema(description = "Object containing rules validation result")
public class RulesValidationResponse {
public class DroolsValidationResponse {
@Builder.Default
@Schema(description = "Show where errors are in the uploaded rules file.")
List<RulesValidationMessage> errors = new ArrayList<>();
/*
@Schema(description = "Show where syntax errors are in the uploaded rules file.")
List<RuleSyntaxErrorMessage> syntaxErrorMessages = new ArrayList<>();
@Builder.Default
@Schema(description = "Show where warnings are in the uploaded rules file.")
List<RulesValidationMessage> warnings = new ArrayList<>();
List<RuleSyntaxWarningMessage> deprecatedWarnings = new ArrayList<>();
@Builder.Default
@Schema(description = "Show deprecated methods are used in the uploaded rules file.")
List<RulesDeprecationMessage> deprecationWarnings = new ArrayList<>();
*/
@Schema(description = "Show where blacklist violations are in the uploaded rules file.")
List<RuleBlacklistErrorMessage> blacklistErrorMessages = new ArrayList<>();
}

View File

@ -0,0 +1,26 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Schema(description = "Object containing a drools blacklist error message, its line, and its column.")
public class RuleBlacklistErrorMessage {
@Schema(description = "The Line where the blacklist violation occurred.")
Integer line;
@Schema(description = "The Column where the blacklist violation occurred.")
Integer column;
@Schema(description = "The violation message.")
String message;
}