From af52f7df8d84e0d11c5cffcf04c6d625a6a9d412 Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Thu, 28 Aug 2025 14:15:18 +0300 Subject: [PATCH] SUPPORT-9339: add sign verify --- backend/pom.xml | 4 + .../ervu/client/fileupload/WebDavClient.java | 27 +- .../EmployeeInfoFileUploadController.java | 4 +- .../model/fileupload/DownloadResponse.java | 2 +- .../fileupload/EmployeeInfoKafkaMessage.java | 19 +- .../java/ervu/model/fileupload/FileInfo.java | 8 + .../EmployeeInfoFileUploadService.java | 359 +++++-- .../EmployeeInfoKafkaMessageService.java | 30 +- .../ervu/audit/model/AuditUploadEvent.java | 14 +- .../ervu/audit/service/AuditService.java | 2 +- .../audit/service/impl/BaseAuditService.java | 4 +- .../security/esia/model/MchdInfoModel.java | 439 +++++++++ .../model/VerifyDocumentSignResponse.java | 35 + .../src/resources/css/components-lkrp.css | 2 +- .../ervu/component/ErvuFileUpload.html | 2 +- .../component/fileupload/ErvuFileUpload.ts | 53 +- pom.xml | 5 + .../Личный кабинет юр лица.page | 923 +++++++++++++++--- 18 files changed, 1662 insertions(+), 270 deletions(-) create mode 100644 backend/src/main/java/ru/micord/ervu/security/esia/model/MchdInfoModel.java create mode 100644 backend/src/main/java/ru/micord/ervu/security/esia/model/VerifyDocumentSignResponse.java diff --git a/backend/pom.xml b/backend/pom.xml index 1c99cb0a..d1592c8b 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -202,6 +202,10 @@ org.apache.tika tika-core + + org.apache.httpcomponents + httpmime + ${project.parent.artifactId} diff --git a/backend/src/main/java/ervu/client/fileupload/WebDavClient.java b/backend/src/main/java/ervu/client/fileupload/WebDavClient.java index 12721d12..2250c1bf 100644 --- a/backend/src/main/java/ervu/client/fileupload/WebDavClient.java +++ b/backend/src/main/java/ervu/client/fileupload/WebDavClient.java @@ -13,10 +13,7 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -89,12 +86,22 @@ public class WebDavClient { } @Retryable(value = {IOException.class}, backoff = @Backoff(delayExpression = "${webdav.retry.delay:500}")) - public String uploadFile(MultipartFile multipartFile) { - String fileName = getNewFilename(multipartFile.getOriginalFilename()); + public Map uploadFiles(Map files) { + String fileCatalog = UUID.randomUUID() + "/"; Sardine sardine = initClient(username, password); try { - return putAndGetUrl(multipartFile.getBytes(), fileName, sardine); + Map result = new HashMap<>(); + for (Map.Entry entry : files.entrySet()) { + String key = entry.getKey(); + MultipartFile file = entry.getValue(); + + if (file != null) { + String fileName = fileCatalog + file.getOriginalFilename(); + result.put(key, putAndGetUrl(file.getBytes(), fileName, sardine)); + } + } + return result; } catch (IOException e) { throw new WebDavException("Failed to put file into WebDAV", e); @@ -109,12 +116,6 @@ public class WebDavClient { } } - private String getNewFilename(String filename) { - int lastIndexOf = filename.lastIndexOf("."); - String fileExtension = lastIndexOf == -1 ? "" : filename.substring(lastIndexOf); - return UUID.randomUUID() + fileExtension; - } - public String putAndGetUrl(byte[] fileBytes, String fileName, Sardine client) throws IOException { if (badServersCache.size() == urls.length) { return null; diff --git a/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java b/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java index 204f7429..219d8807 100644 --- a/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java +++ b/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java @@ -26,13 +26,15 @@ public class EmployeeInfoFileUploadController { @RequestMapping(value = "/employee/document", method = RequestMethod.POST) public ResponseEntity saveEmployeeInformationFile( @RequestParam("file") MultipartFile multipartFile, + @RequestParam("signFile") MultipartFile signFile, + @RequestParam(value = "mchdFile", required = false) MultipartFile mchdFile, @RequestHeader("X-Employee-Info-File-Form-Type") String formType, @RequestHeader("Client-Time-Zone") String clientTimeZone) { String offset = ZonedDateTime.now(TimeZone.getTimeZone(clientTimeZone).toZoneId()) .getOffset().getId(); - if (this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType, offset)) { + if (this.fileUploadService.saveEmployeeInformationFiles(multipartFile, signFile, mchdFile, formType, offset)) { return ResponseEntity.ok("File successfully uploaded."); } diff --git a/backend/src/main/java/ervu/model/fileupload/DownloadResponse.java b/backend/src/main/java/ervu/model/fileupload/DownloadResponse.java index 883f749b..297e6399 100644 --- a/backend/src/main/java/ervu/model/fileupload/DownloadResponse.java +++ b/backend/src/main/java/ervu/model/fileupload/DownloadResponse.java @@ -3,5 +3,5 @@ package ervu.model.fileupload; /** * @author r.latypov */ -public record DownloadResponse(UploadOrgInfo orgInfo, FileInfo fileInfo) { +public record DownloadResponse(UploadOrgInfo orgInfo, FileInfo[] filesInfo) { } diff --git a/backend/src/main/java/ervu/model/fileupload/EmployeeInfoKafkaMessage.java b/backend/src/main/java/ervu/model/fileupload/EmployeeInfoKafkaMessage.java index a6f1e08f..a3d27410 100644 --- a/backend/src/main/java/ervu/model/fileupload/EmployeeInfoKafkaMessage.java +++ b/backend/src/main/java/ervu/model/fileupload/EmployeeInfoKafkaMessage.java @@ -1,5 +1,6 @@ package ervu.model.fileupload; +import java.util.Arrays; import java.util.Objects; /** @@ -7,19 +8,19 @@ import java.util.Objects; */ public class EmployeeInfoKafkaMessage { private final UploadOrgInfo orgInfo; - private final FileInfo fileInfo; + private final FileInfo[] filesInfo; - public EmployeeInfoKafkaMessage(UploadOrgInfo orgInfo, FileInfo fileInfo) { + public EmployeeInfoKafkaMessage(UploadOrgInfo orgInfo, FileInfo[] filesInfo) { this.orgInfo = orgInfo; - this.fileInfo = fileInfo; + this.filesInfo = filesInfo; } public UploadOrgInfo getOrgInfo() { return orgInfo; } - public FileInfo getFileInfo() { - return fileInfo; + public FileInfo[] getFilesInfo() { + return filesInfo; } @Override @@ -27,19 +28,21 @@ public class EmployeeInfoKafkaMessage { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EmployeeInfoKafkaMessage that = (EmployeeInfoKafkaMessage) o; - return Objects.equals(orgInfo, that.orgInfo) && Objects.equals(fileInfo, that.fileInfo); + return Objects.equals(orgInfo, that.orgInfo) && Arrays.equals(filesInfo, that.filesInfo); } @Override public int hashCode() { - return Objects.hash(orgInfo, fileInfo); + int result = Objects.hash(orgInfo); + result = 31 * result + Arrays.hashCode(filesInfo); + return result; } @Override public String toString() { return "KafkaMessage{" + "uploadOrgInfo=" + orgInfo + - ", fileInfo=" + fileInfo + + ", fileInfo=" + Arrays.toString(filesInfo) + '}'; } } diff --git a/backend/src/main/java/ervu/model/fileupload/FileInfo.java b/backend/src/main/java/ervu/model/fileupload/FileInfo.java index 5141e433..1603db83 100644 --- a/backend/src/main/java/ervu/model/fileupload/FileInfo.java +++ b/backend/src/main/java/ervu/model/fileupload/FileInfo.java @@ -69,6 +69,14 @@ public class FileInfo { return fileStatus; } + public void setFileStatus(FileStatus fileStatus) { + this.fileStatus = fileStatus; + } + + public void setFileUrl(String fileUrl) { + this.fileUrl = fileUrl; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java index c9eb916e..d21a7371 100644 --- a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java +++ b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java @@ -1,21 +1,29 @@ package ervu.service.fileupload; import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.sql.Timestamp; +import java.time.Duration; import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Locale; -import java.util.UUID; +import java.util.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import ervu.client.fileupload.WebDavClient; -import ervu.model.fileupload.DownloadResponse; -import ervu.model.fileupload.EmployeeInfoFileFormType; -import ervu.model.fileupload.EmployeeInfoKafkaMessage; -import ervu.model.fileupload.FileInfo; -import ervu.model.fileupload.FileStatus; +import ervu.model.fileupload.*; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.tika.Tika; import org.apache.tika.mime.MediaType; @@ -32,8 +40,12 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import ru.micord.ervu.audit.service.AuditService; import ru.micord.ervu.exception.JsonParsingException; +import ru.micord.ervu.security.esia.config.EsiaConfig; +import ru.micord.ervu.security.esia.exception.EsiaException; import ru.micord.ervu.security.esia.model.EmployeeModel; +import ru.micord.ervu.security.esia.model.MchdInfoModel; import ru.micord.ervu.security.esia.model.PersonModel; +import ru.micord.ervu.security.esia.model.VerifyDocumentSignResponse; import ru.micord.ervu.security.esia.service.UlDataService; import ru.micord.ervu.security.esia.EsiaAuthInfoStore; import ru.micord.ervu.security.webbpm.jwt.UserIdsPair; @@ -59,6 +71,8 @@ public class EmployeeInfoFileUploadService { private final InteractionService interactionService; private final UlDataService ulDataService; private final AuditService auditService; + private final ObjectMapper objectMapper; + private final EsiaConfig esiaConfig; @Value("${av.kafka.message.topic.name}") private String kafkaTopicName; @@ -69,24 +83,35 @@ public class EmployeeInfoFileUploadService { @Qualifier("avTemplate") KafkaTemplate kafkaTemplate, InteractionService interactionService, UlDataService ulDataService, - AuditService auditService) { + AuditService auditService, + ObjectMapper objectMapper, + EsiaConfig esiaConfig) { this.webDavClient = webDavClient; this.employeeInfoKafkaMessageService = employeeInfoKafkaMessageService; this.kafkaTemplate = kafkaTemplate; this.interactionService = interactionService; this.ulDataService = ulDataService; this.auditService = auditService; + this.objectMapper = objectMapper; + this.esiaConfig = esiaConfig; } - public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType, - String offset) { + public boolean saveEmployeeInformationFiles(MultipartFile multipartFile, + MultipartFile signFile, MultipartFile mchdFile, String formType, + String offset) { UserIdsPair userIdsPair = SecurityUtil.getUserIdsPair(); + LocalDateTime now = LocalDateTime.now(); + String departureDateTime = DateUtils.convertToString(now); - if (userIdsPair == null || !isValid(multipartFile)) { + if (userIdsPair == null || !isValidCsvWithSig(multipartFile, signFile)) { return false; } String fileId = UUID.randomUUID().toString(); + String signFileId = UUID.randomUUID().toString(); String fileName = multipartFile.getOriginalFilename(); + long fileSize = multipartFile.getSize(); + String signFileName = signFile.getOriginalFilename(); + long signFileSize = signFile.getSize(); EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf( formType); String esiaUserId = userIdsPair.getEsiaUserId(); @@ -94,79 +119,207 @@ public class EmployeeInfoFileUploadService { String accessToken = EsiaAuthInfoStore.getAccessToken(esiaUserId); EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); PersonModel personModel = employeeModel.getPerson(); - - String fileUploadUrl = this.webDavClient.uploadFile(multipartFile); + EmployeeModel chiefModel = ulDataService.getChiefEmployeeModel(accessToken); + VerifyDocumentSignResponse verifyDocumentSignResponse = validateSign(multipartFile, signFile); FileStatus fileStatus = new FileStatus(); - fileStatus.setStatus(fileUploadUrl == null ? "Невозможно проверить файл ЛК РП" : "Загрузка"); - LocalDateTime now = LocalDateTime.now(); - interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, - employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now), - convertToFio(personModel.getFirstName(), personModel.getMiddleName(), - personModel.getLastName() - ), - ervuId + String fio = convertToFio(personModel.getFirstName(), personModel.getMiddleName(), + personModel.getLastName() ); - - long fileSize = multipartFile.getSize(); - String departureDateTime = DateUtils.convertToString(now); - EmployeeInfoKafkaMessage kafkaMessage = employeeInfoKafkaMessageService.getKafkaMessage( - fileId, - fileUploadUrl, - fileName, - employeeInfoFileFormType, - departureDateTime, - accessToken, - offset, - fileStatus, - ervuId, - esiaUserId, - personModel, - fileSize + UploadOrgInfo uploadOrgInfo = employeeInfoKafkaMessageService.getOrgInfo(accessToken, ervuId, + esiaUserId, personModel ); - - if (fileUploadUrl != null) { - fileStatus.setCode(FILE_UPLOADED.getCode()); - fileStatus.setDescription("Файл принят до проверки на вирусы"); - String jsonMessage = getJsonKafkaMessage(kafkaMessage); - return sendMessage(jsonMessage); + FileInfo fileInfo = employeeInfoKafkaMessageService.getFileInfo(fileId, null, fileName, + employeeInfoFileFormType, departureDateTime, offset, null, fileSize + ); + FileInfo signFileInfo = employeeInfoKafkaMessageService.getFileInfo(signFileId, null, + signFileName, employeeInfoFileFormType, departureDateTime, offset, null, signFileSize + ); + if (verifyDocumentSignResponse.getSignVerifyResult() != null) { + fileStatus.setStatus("Некорректная ЭП"); + fileInfo.setFileStatus(fileStatus); + signFileInfo.setFileStatus(fileStatus); + interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, + employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now), + fio, ervuId + ); + auditService.processUploadEvent(uploadOrgInfo, new FileInfo[] { + fileInfo, signFileInfo + }); + //Заменить на свою ошибку + throw new RuntimeException(verifyDocumentSignResponse.getSignVerifyResult()); + } + String signerInfo = verifyDocumentSignResponse.getSignerInfo(); + Map signerInfoMap = parseKeyValuePairs(signerInfo); + String chiefMiddleName = chiefModel.getPerson().getMiddleName(); + String chiefLastName = chiefModel.getPerson().getLastName(); + String chiefFirstName = chiefModel.getPerson().getFirstName(); + if (signerInfoMap.get("SN").equalsIgnoreCase(chiefLastName) && signerInfoMap.get("G") + .equalsIgnoreCase(chiefFirstName + " " + chiefMiddleName) && signerInfoMap.get("O") + .equalsIgnoreCase(uploadOrgInfo.getOrgName())) { + Map hashMap = this.webDavClient.uploadFiles(Map.of( + "fileUrl", multipartFile, + "signUrl", signFile + )); + String fileUploadUrl = hashMap.get("fileUrl"); + String signFileUploadUrl = hashMap.get("signFile"); + fileStatus.setStatus(fileUploadUrl == null || signFileUploadUrl == null + ? "Невозможно проверить файл ЛК РП" + : "Загрузка"); + interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, + employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now), + convertToFio(personModel.getFirstName(), personModel.getMiddleName(), + personModel.getLastName() + ), + ervuId + ); + fileInfo.setFileStatus(fileStatus); + signFileInfo.setFileStatus(fileStatus); + if (fileUploadUrl == null || signFileUploadUrl == null) { + LOGGER.error("Failed to upload files: {}, {}", fileName, signFileName); + fileStatus.setCode(FILE_NOT_CHECKED.getCode()); + fileStatus.setDescription( + "Невозможно проверить файл по причине недоступности или ошибки в работе антивируса"); + auditService.processUploadEvent(uploadOrgInfo, new FileInfo[] {fileInfo, signFileInfo}); + return false; + } + else { + fileInfo.setFileUrl(fileUploadUrl); + signFileInfo.setFileUrl(signFileUploadUrl); + EmployeeInfoKafkaMessage kafkaMessage = employeeInfoKafkaMessageService.getKafkaMessage( + uploadOrgInfo, new FileInfo[] { + fileInfo, signFileInfo + }); + fileStatus.setCode(FILE_UPLOADED.getCode()); + fileStatus.setDescription("Файл принят до проверки на вирусы"); + String jsonMessage = getJsonKafkaMessage(kafkaMessage); + return sendMessage(jsonMessage); + } } else { - LOGGER.error("Failed to upload file: {}", fileName); - fileStatus.setCode(FILE_NOT_CHECKED.getCode()); - fileStatus.setDescription("Невозможно проверить файл по причине недоступности или ошибки в работе антивируса"); - auditService.processUploadEvent(kafkaMessage.getOrgInfo(), kafkaMessage.getFileInfo()); - return false; + if (mchdFile == null) { + fileStatus.setStatus("Некорректная МЧД"); + interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, + employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now), + convertToFio(personModel.getFirstName(), personModel.getMiddleName(), + personModel.getLastName() + ), + ervuId + ); + auditService.processUploadEvent(uploadOrgInfo, new FileInfo[] { + fileInfo, signFileInfo + }); + //Заменить на свою ошибку + throw new RuntimeException(); + } + String mchdFileId = UUID.randomUUID().toString(); + String mchdFileName = mchdFile.getOriginalFilename(); + long mchdFileSize = mchdFile.getSize(); + FileInfo mchdFileInfo = employeeInfoKafkaMessageService.getFileInfo(mchdFileId, null, + mchdFileName, employeeInfoFileFormType, departureDateTime, offset, fileStatus, + mchdFileSize + ); + if (validateMchd(mchdFile, accessToken)) { + Map hashMap = this.webDavClient.uploadFiles(Map.of( + "fileUrl", multipartFile, + "signUrl", signFile, + "mchdFile", mchdFile + )); + String fileUploadUrl = hashMap.get("fileUrl"); + String signFileUploadUrl = hashMap.get("signFile"); + String mchdFileUploadUrl = hashMap.get("mchdFile"); + fileStatus.setStatus( + fileUploadUrl == null || signFileUploadUrl == null || mchdFileUploadUrl == null + ? "Невозможно проверить файл ЛК РП" + : "Загрузка"); + interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, + employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now), + convertToFio(personModel.getFirstName(), personModel.getMiddleName(), + personModel.getLastName() + ), + ervuId + ); + fileInfo.setFileStatus(fileStatus); + signFileInfo.setFileStatus(fileStatus); + if (fileUploadUrl == null || signFileUploadUrl == null || mchdFileUploadUrl == null) { + LOGGER.error("Failed to upload files: {}, {}, {}", fileName, signFileName, + mchdFileName + ); + fileStatus.setCode(FILE_NOT_CHECKED.getCode()); + fileStatus.setDescription( + "Невозможно проверить файл по причине недоступности или ошибки в работе антивируса"); + auditService.processUploadEvent(uploadOrgInfo, + new FileInfo[] {fileInfo, signFileInfo, mchdFileInfo} + ); + return false; + } + else { + fileInfo.setFileUrl(fileUploadUrl); + signFileInfo.setFileUrl(signFileUploadUrl); + mchdFileInfo.setFileUrl(mchdFileUploadUrl); + EmployeeInfoKafkaMessage kafkaMessage = employeeInfoKafkaMessageService.getKafkaMessage( + uploadOrgInfo, new FileInfo[] { + fileInfo, signFileInfo, mchdFileInfo + }); + fileStatus.setCode(FILE_UPLOADED.getCode()); + fileStatus.setDescription("Файл принят до проверки на вирусы"); + String jsonMessage = getJsonKafkaMessage(kafkaMessage); + return sendMessage(jsonMessage); + } + } + else { + fileStatus.setStatus("Некорректная МЧД"); + interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, + employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now), + convertToFio(personModel.getFirstName(), personModel.getMiddleName(), + personModel.getLastName() + ), + ervuId + ); + auditService.processUploadEvent(uploadOrgInfo, new FileInfo[] { + fileInfo, signFileInfo, mchdFileInfo + }); + //Заменить на свою ошибку + throw new RuntimeException(); + } } } - private boolean isValid(MultipartFile multipartFile) { - - if (multipartFile == null || multipartFile.getOriginalFilename() == null) { + private boolean isValidCsvWithSig(MultipartFile file, MultipartFile signFile) { + if (file == null || signFile == null || file.getOriginalFilename() == null + || signFile.getOriginalFilename() == null) { return false; } - String fileName = multipartFile.getOriginalFilename(); + Tika tika = new Tika(); + MimeTypes defaultMimeTypes = MimeTypes.getDefaultMimeTypes(); + try { - String contentType = new Tika().detect(multipartFile.getBytes()); - MimeTypes defaultMimeTypes = MimeTypes.getDefaultMimeTypes(); - MimeType mimeType = defaultMimeTypes.forName(contentType); - boolean isCsv = mimeType.getType().equals(MediaType.TEXT_PLAIN) - && fileName.toLowerCase(Locale.getDefault()).endsWith(".csv"); + String fileName = file.getOriginalFilename().toLowerCase(Locale.getDefault()); + String signFileName = signFile.getOriginalFilename().toLowerCase(Locale.getDefault()); + + String fileContentType = tika.detect(file.getBytes()); + String signContentType = tika.detect(signFile.getBytes()); + + MimeType fileMimeType = defaultMimeTypes.forName(fileContentType); + MimeType signMimeType = defaultMimeTypes.forName(signContentType); + + boolean isCsv = + MediaType.TEXT_PLAIN.equals(fileMimeType.getType()) && fileName.endsWith(".csv"); + boolean isSig = + "application/pkcs7-signature".equals(signMimeType.toString()) && signFileName.endsWith( + ".sig"); if (!isCsv) { - LOGGER.info("Trying to upload file={} with wrong mime type={}", - fileName, mimeType - ); + LOGGER.info("Invalid main file: name={}, mimeType={}", fileName, fileMimeType); } - return isCsv; + if (!isSig) { + LOGGER.info("Invalid signature file: name={}, mimeType={}", signFileName, signMimeType); + } + + return isCsv && isSig; } - catch (MimeTypeException e) { - LOGGER.error( - "Couldn't get mime type from bytes for file=" + fileName, e); - return false; - } - catch (IOException e) { - LOGGER.error("Error while checking file type, file=" + fileName, - e + catch (MimeTypeException | IOException e) { + LOGGER.error("Failed to process files: {}, {}", file.getOriginalFilename(), + signFile.getOriginalFilename(), e ); return false; } @@ -206,14 +359,14 @@ public class EmployeeInfoFileUploadService { try { DownloadResponse downloadResponse = mapper.readValue(kafkaMessage, DownloadResponse.class); - FileInfo fileInfo = downloadResponse.fileInfo(); + FileInfo fileInfo = downloadResponse.filesInfo()[0]; String statusCode = fileInfo.getFileStatus().getCode(); if (Arrays.asList(FILE_INFECTED.getCode(), FILE_CLEAN.getCode()).contains(statusCode)) { interactionService.delete(fileInfo.getFileId(), downloadResponse.orgInfo().getOrgId()); } else if (statusCode.equals(FILE_NOT_CHECKED.getCode())) { - auditService.processUploadEvent(downloadResponse.orgInfo(), downloadResponse.fileInfo()); + auditService.processUploadEvent(downloadResponse.orgInfo(), downloadResponse.filesInfo()); interactionService.updateStatus(fileInfo.getFileId(), fileInfo.getFileStatus().getStatus(), downloadResponse.orgInfo().getOrgId() ); @@ -223,4 +376,66 @@ public class EmployeeInfoFileUploadService { throw new JsonParsingException(String.format("Fail get json from: %s", kafkaMessage), e); } } + + private VerifyDocumentSignResponse validateSign(MultipartFile file, MultipartFile signFile) { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpPost upload = new HttpPost(esiaConfig.getSignUrl()); + HttpEntity multipart = MultipartEntityBuilder.create() + .addBinaryBody("data", file.getBytes(), ContentType.APPLICATION_OCTET_STREAM, + file.getOriginalFilename() + ) + .addBinaryBody("sign", signFile.getBytes(), ContentType.APPLICATION_OCTET_STREAM, + signFile.getOriginalFilename() + ) + .build(); + upload.setEntity(multipart); + try (CloseableHttpResponse response = httpClient.execute(upload)) { + int statusCode = response.getStatusLine().getStatusCode(); + String body = EntityUtils.toString(response.getEntity()); + if (statusCode != 200) { + throw new RuntimeException(statusCode + " " + body); + } + return objectMapper.readValue(body, VerifyDocumentSignResponse.class); + } + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + private boolean validateMchd(MultipartFile mchdFile, String accessToken) { + try { + String mchdGuid = getMchdGuid(mchdFile); + MchdInfoModel mchdInfoModel = ulDataService.getMchdInfo(mchdGuid, accessToken); + mchdInfoModel. + + } + catch (Exception e) { + throw new EsiaException(e); + } + } + + private Map parseKeyValuePairs(String input) { + Map map = new HashMap<>(); + String[] pairs = input.split(","); + + for (String pair : pairs) { + String[] keyValue = pair.split("=", 2); + if (keyValue.length == 2) { + map.put(keyValue[0].trim(), keyValue[1].trim()); + } + } + return map; + } + + private String getMchdGuid(MultipartFile mcnhdFile) { + try { + return "asdasdasd"; + + } + catch (Exception e) { + throw new EsiaException(e); + } + } + } diff --git a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoKafkaMessageService.java b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoKafkaMessageService.java index d8414f2b..90413bab 100644 --- a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoKafkaMessageService.java +++ b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoKafkaMessageService.java @@ -29,20 +29,26 @@ public class EmployeeInfoKafkaMessageService { PersonModel personModel, long fileSize) { return new EmployeeInfoKafkaMessage( getOrgInfo(accessToken, ervuId, prnOid, personModel), - getFileInfo( - fileId, - fileUrl, - fileName, - formType, - departureDateTime, - offset, - fileStatus, - fileSize - ) + new FileInfo[] { + getFileInfo( + fileId, + fileUrl, + fileName, + formType, + departureDateTime, + offset, + fileStatus, + fileSize + ), + } ); } - private FileInfo getFileInfo(String fileId, String fileUrl, String fileName, + public EmployeeInfoKafkaMessage getKafkaMessage(UploadOrgInfo orgInfo, FileInfo[] filesInfo) { + return new EmployeeInfoKafkaMessage(orgInfo, filesInfo); + } + + public FileInfo getFileInfo(String fileId, String fileUrl, String fileName, EmployeeInfoFileFormType formType, String departureDateTime, String offset, FileStatus fileStatus, long fileSize) { return new FileInfo( @@ -58,7 +64,7 @@ public class EmployeeInfoKafkaMessageService { ); } - private UploadOrgInfo getOrgInfo(String accessToken, String ervuId, String prnOid, PersonModel personModel) { + public UploadOrgInfo getOrgInfo(String accessToken, String ervuId, String prnOid, PersonModel personModel) { OrganizationModel organizationModel = ulDataService.getOrganizationModel(accessToken); SenderInfo senderInfo = new SenderInfo(); senderInfo.setFirstName(personModel.getFirstName()); diff --git a/backend/src/main/java/ru/micord/ervu/audit/model/AuditUploadEvent.java b/backend/src/main/java/ru/micord/ervu/audit/model/AuditUploadEvent.java index 65270471..12cca209 100644 --- a/backend/src/main/java/ru/micord/ervu/audit/model/AuditUploadEvent.java +++ b/backend/src/main/java/ru/micord/ervu/audit/model/AuditUploadEvent.java @@ -10,11 +10,11 @@ import ervu.model.fileupload.UploadOrgInfo; public class AuditUploadEvent { private UploadOrgInfo orgInfo; - private FileInfo fileInfo; + private FileInfo[] filesInfo; - public AuditUploadEvent(UploadOrgInfo orgInfo, FileInfo fileInfo) { + public AuditUploadEvent(UploadOrgInfo orgInfo, FileInfo[] filesInfo) { this.orgInfo = orgInfo; - this.fileInfo = fileInfo; + this.filesInfo = filesInfo; } public UploadOrgInfo getOrgInfo() { @@ -25,11 +25,11 @@ public class AuditUploadEvent { this.orgInfo = orgInfo; } - public FileInfo getFileInfo() { - return fileInfo; + public FileInfo[] getFilesInfo() { + return filesInfo; } - public void setFileInfo(FileInfo fileInfo) { - this.fileInfo = fileInfo; + public void setFilesInfo(FileInfo[] filesInfo) { + this.filesInfo = filesInfo; } } diff --git a/backend/src/main/java/ru/micord/ervu/audit/service/AuditService.java b/backend/src/main/java/ru/micord/ervu/audit/service/AuditService.java index d9f9ea8d..c189f19a 100644 --- a/backend/src/main/java/ru/micord/ervu/audit/service/AuditService.java +++ b/backend/src/main/java/ru/micord/ervu/audit/service/AuditService.java @@ -16,7 +16,7 @@ public interface AuditService { void processAuthEvent(HttpServletRequest request, OrgInfo orgInfo, String prnOid, String status, String eventType); - void processUploadEvent(UploadOrgInfo uploadOrgInfo, FileInfo fileInfo); + void processUploadEvent(UploadOrgInfo uploadOrgInfo, FileInfo[] filesInfo); void processDownloadEvent(HttpServletRequest request, long fileSize, String fileName, int formatRegistry, String status, String s3FileUrl); diff --git a/backend/src/main/java/ru/micord/ervu/audit/service/impl/BaseAuditService.java b/backend/src/main/java/ru/micord/ervu/audit/service/impl/BaseAuditService.java index c9777870..14a5a2cc 100644 --- a/backend/src/main/java/ru/micord/ervu/audit/service/impl/BaseAuditService.java +++ b/backend/src/main/java/ru/micord/ervu/audit/service/impl/BaseAuditService.java @@ -110,10 +110,10 @@ public class BaseAuditService implements AuditService { } @Override - public void processUploadEvent(UploadOrgInfo orgInfo, FileInfo fileInfo) { + public void processUploadEvent(UploadOrgInfo orgInfo, FileInfo[] filesInfo) { AuditUploadEvent auditUploadEvent = new AuditUploadEvent( orgInfo, - fileInfo + filesInfo ); String message = convertToMessage(auditUploadEvent); diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/model/MchdInfoModel.java b/backend/src/main/java/ru/micord/ervu/security/esia/model/MchdInfoModel.java new file mode 100644 index 00000000..662dccba --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/model/MchdInfoModel.java @@ -0,0 +1,439 @@ +package ru.micord.ervu.security.esia.model; + +import java.io.Serializable; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + + +/** + * @author Eduard Tihomirov + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class MchdInfoModel implements Serializable { + private static final long serialVersionUID = 1L; + + private List stateFacts; + private String guid; + private String number; + private String status; + private String issuedOn; + private String expiredOn; + + private Principals principals; + private Agents agents; + private Systems systems; + private Revocations revocations; + + public List getStateFacts() { + return stateFacts; + } + + public void setStateFacts(List stateFacts) { + this.stateFacts = stateFacts; + } + + public String getGuid() { + return guid; + } + + public void setGuid(String guid) { + this.guid = guid; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getIssuedOn() { + return issuedOn; + } + + public void setIssuedOn(String issuedOn) { + this.issuedOn = issuedOn; + } + + public String getExpiredOn() { + return expiredOn; + } + + public void setExpiredOn(String expiredOn) { + this.expiredOn = expiredOn; + } + + public Principals getPrincipals() { + return principals; + } + + public void setPrincipals(Principals principals) { + this.principals = principals; + } + + public Agents getAgents() { + return agents; + } + + public void setAgents(Agents agents) { + this.agents = agents; + } + + public Systems getSystems() { + return systems; + } + + public void setSystems(Systems systems) { + this.systems = systems; + } + + public Revocations getRevocations() { + return revocations; + } + + public void setRevocations(Revocations revocations) { + this.revocations = revocations; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Principals implements Serializable { + private List stateFacts; + private int size; + private List elements; + + public List getStateFacts() { + return stateFacts; + } + + public void setStateFacts(List stateFacts) { + this.stateFacts = stateFacts; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public List getElements() { + return elements; + } + + public void setElements(List elements) { + this.elements = elements; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class PrincipalElement implements Serializable { + private List stateFacts; + private Organization organization; + + public List getStateFacts() { + return stateFacts; + } + + public void setStateFacts(List stateFacts) { + this.stateFacts = stateFacts; + } + + public Organization getOrganization() { + return organization; + } + + public void setOrganization(Organization organization) { + this.organization = organization; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Agents implements Serializable { + private List stateFacts; + private int size; + private List elements; + + public List getStateFacts() { + return stateFacts; + } + + public void setStateFacts(List stateFacts) { + this.stateFacts = stateFacts; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public List getElements() { + return elements; + } + + public void setElements(List elements) { + this.elements = elements; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class AgentElement implements Serializable { + private List stateFacts; + private Person person; + + public List getStateFacts() { + return stateFacts; + } + + public void setStateFacts(List stateFacts) { + this.stateFacts = stateFacts; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Systems implements Serializable { + private List stateFacts; + private int size; + private List elements; + + public List getStateFacts() { + return stateFacts; + } + + public void setStateFacts(List stateFacts) { + this.stateFacts = stateFacts; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public List getElements() { + return elements; + } + + public void setElements(List elements) { + this.elements = elements; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class SystemElement implements Serializable { + private String nsiId; + private String name; + + public String getNsiId() { + return nsiId; + } + + public void setNsiId(String nsiId) { + this.nsiId = nsiId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Revocations implements Serializable { + private List stateFacts; + private int size; + private List elements; + + public List getStateFacts() { + return stateFacts; + } + + public void setStateFacts(List stateFacts) { + this.stateFacts = stateFacts; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public List getElements() { + return elements; + } + + public void setElements(List elements) { + this.elements = elements; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class RevocationElement implements Serializable { + private String number; + private String poaGuid; + private String revokedOn; + private Principals principals; + private Agents agents; + private Signatory signatory; + private Systems systems; + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getPoaGuid() { + return poaGuid; + } + + public void setPoaGuid(String poaGuid) { + this.poaGuid = poaGuid; + } + + public String getRevokedOn() { + return revokedOn; + } + + public void setRevokedOn(String revokedOn) { + this.revokedOn = revokedOn; + } + + public Principals getPrincipals() { + return principals; + } + + public void setPrincipals(Principals principals) { + this.principals = principals; + } + + public Agents getAgents() { + return agents; + } + + public void setAgents(Agents agents) { + this.agents = agents; + } + + public Signatory getSignatory() { + return signatory; + } + + public void setSignatory(Signatory signatory) { + this.signatory = signatory; + } + + public Systems getSystems() { + return systems; + } + + public void setSystems(Systems systems) { + this.systems = systems; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Organization implements Serializable { + private String ogrn; + private String name; + private String inn; + private String kpp; + + public String getOgrn() { + return ogrn; + } + + public void setOgrn(String ogrn) { + this.ogrn = ogrn; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getInn() { + return inn; + } + + public void setInn(String inn) { + this.inn = inn; + } + + public String getKpp() { + return kpp; + } + + public void setKpp(String kpp) { + this.kpp = kpp; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Person implements Serializable { + private String snils; + + public String getSnils() { + return snils; + } + + public void setSnils(String snils) { + this.snils = snils; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Signatory implements Serializable { + private List stateFacts; + private Person person; + + public List getStateFacts() { + return stateFacts; + } + + public void setStateFacts(List stateFacts) { + this.stateFacts = stateFacts; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/model/VerifyDocumentSignResponse.java b/backend/src/main/java/ru/micord/ervu/security/esia/model/VerifyDocumentSignResponse.java new file mode 100644 index 00000000..9485a74c --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/model/VerifyDocumentSignResponse.java @@ -0,0 +1,35 @@ +package ru.micord.ervu.security.esia.model; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Eduard Tihomirov + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class VerifyDocumentSignResponse implements Serializable { + + private static final long serialVersionUID = 1L; + @JsonProperty("error_code") + private String errorCode; + @JsonProperty("signer_subject") + private String signerInfo; + + public String getSignVerifyResult() { + return errorCode; + } + + public void setSignVerifyResult(String signVerifyResult) { + this.errorCode = signVerifyResult; + } + + public String getSignerInfo() { + return signerInfo; + } + + public void setSignerInfo(String signerInfo) { + this.signerInfo = signerInfo; + } +} \ No newline at end of file diff --git a/frontend/src/resources/css/components-lkrp.css b/frontend/src/resources/css/components-lkrp.css index dbbb3748..9c84f7de 100644 --- a/frontend/src/resources/css/components-lkrp.css +++ b/frontend/src/resources/css/components-lkrp.css @@ -855,7 +855,7 @@ overflow: hidden; padding-left: 56px; } -.webbpm.ervu_lkrp_ul .modal.show ervu-file-upload .selected-file .selected-file-name::before { +.webbpm.ervu_lkrp_ul .modal.show ervu-file-upload .selected-file .icon-csv::before { position: absolute; content: url(../img/svg/file-csv.svg); top: -2px; diff --git a/frontend/src/resources/template/ervu/component/ErvuFileUpload.html b/frontend/src/resources/template/ervu/component/ErvuFileUpload.html index 1dc7cd7c..663ae556 100644 --- a/frontend/src/resources/template/ervu/component/ErvuFileUpload.html +++ b/frontend/src/resources/template/ervu/component/ErvuFileUpload.html @@ -18,7 +18,7 @@ hidden>
- {{item?.file?.name}} + {{item?.file?.name}} {{item?.file?.size/1024/1024 | number: '.2'}} MB
diff --git a/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts b/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts index 4ed140be..b79f69f9 100644 --- a/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts +++ b/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts @@ -5,7 +5,7 @@ import { Event, MessagesService, UnsupportedOperationError, - AppConfigService + AppConfigService, ObjectRef } from "@webbpm/base-package"; import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from "@angular/core"; import {FileItem, FileUploader} from "ng2-file-upload"; @@ -23,6 +23,10 @@ import {TokenConstants} from "../../../modules/security/TokenConstants"; export class ErvuFileUpload extends InputControl { private static readonly BACKEND_URL: string = "backend.context"; + @ObjectRef() + public signFileUploadRef: ErvuFileUpload; + @ObjectRef() + public mchdFileUploadRef: ErvuFileUpload; @NotNull("true") public selectFileFieldText: string; @NotNull("true") @@ -137,24 +141,32 @@ export class ErvuFileUpload extends InputControl { private setUploaderMethods() { this.uploader.onBeforeUploadItem = (fileItem: FileItem) => { + const additionalParams: any = { + signFile: this.signFileUploadRef.uploader.queue[0]._file + }; + + if (this.mchdFileUploadRef && this.mchdFileUploadRef.uploader.queue.length > 0) { + additionalParams.mchdFile = this.mchdFileUploadRef.uploader.queue[0]._file; + } //refresh headers - this.uploader.setOptions({ - headers: [ - { - name: "X-Employee-Info-File-Form-Type", - value: EmployeeInfoFileFormType[this.formType] - }, - { - name: "Client-Time-Zone", - value: Intl.DateTimeFormat().resolvedOptions().timeZone - }, - { - name: TokenConstants.CSRF_HEADER_NAME, - value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME) - } - ] - }); + this.uploader.setOptions({ + headers: [ + { + name: "X-Employee-Info-File-Form-Type", + value: EmployeeInfoFileFormType[this.formType] + }, + { + name: "Client-Time-Zone", + value: Intl.DateTimeFormat().resolvedOptions().timeZone + }, + { + name: TokenConstants.CSRF_HEADER_NAME, + value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME) + } + ], + additionalParameter: additionalParams + }); this.fileUploadStartEvent.trigger(); this.isDropZoneVisible = false; this.isFilesListVisible = false; @@ -221,6 +233,13 @@ export class ErvuFileUpload extends InputControl { return undefined; } + getFileIconClass(): string { + if (this.extensionFilter && this.extensionFilter.length > 0) { + return `icon-${this.extensionFilter[0].toLowerCase()}`; + } + return 'icon-default'; + } + subscribeToModelChange() { //empty because there is no ngModel here } diff --git a/pom.xml b/pom.xml index f83faf03..7f7dec9d 100644 --- a/pom.xml +++ b/pom.xml @@ -300,6 +300,11 @@ sardine 5.12 + + org.apache.httpcomponents + httpmime + 4.5.14 + diff --git a/resources/src/main/resources/business-model/Личный кабинет юр лица.page b/resources/src/main/resources/business-model/Личный кабинет юр лица.page index 2df73da3..4e016a8e 100644 --- a/resources/src/main/resources/business-model/Личный кабинет юр лица.page +++ b/resources/src/main/resources/business-model/Личный кабинет юр лица.page @@ -1241,7 +1241,7 @@ initialValue - "Перед выбором файла убедитесь, что все данные в файле введены корректно" + "Перед выбором файла убедитесь, что все данные в файле введены корректно. \u003cbr\u003eФайл подписан усиленной квалифицированной электронной подписью." @@ -1359,38 +1359,7 @@ 529140ee-17b6-44e9-8f2d-a097b4bc044d Текст false - false - - - - cssClasses - - - - "subtitle" - - - - - - - - - - - initialValue - - "Сведения о приеме на работу (увольнении), зачислении в образовательную организацию (отчислении)" - - - - - - - - - false - + true 9d1b5af1-0b8f-4b1b-b9a5-c2e6acf72d91 @@ -1477,6 +1446,523 @@ +false + + + + 9d1b5af1-0b8f-4b1b-b9a5-c2e6acf72d91 + 7ea732e4-71c7-4325-8569-462acbea89b8 + Вертикальный контейнер + true + true + + + + 9d1b5af1-0b8f-4b1b-b9a5-c2e6acf72d91 + 7ea732e4-71c7-4325-8569-462acbea89b8 + Вертикальный контейнер + true + false + + + + + + + ba24d307-0b91-4299-ba82-9d0b52384ff2 + 529140ee-17b6-44e9-8f2d-a097b4bc044d + Текст + false + false + + + + cssClasses + + + + "subtitle" + + + + + + + + + + + initialValue + + "Сведения о приеме на работу (увольнении), зачислении в образовательную организацию (отчислении)" + + + + + + + + +false + + + + 5694e7c5-bbb5-4d23-be6c-7ad71b8ad38c + 7d338f47-6d12-4040-ba18-f31f520dce8d + FileUploadV2 + false + false + false + + + + collectible + + false + + + + cssClasses + + + + "btn-main" + + + + + + extensionFilter + + + + "csv" + + + + + + + formType + + "FORM_9" + + + + maxFileSizeMb + + 5.0 + + + + maxFilesToUpload + + 1.0 + + + + mchdFileUploadRef + + {"objectId":"1dc25461-a60e-456b-93cb-cc0f28dc347b","packageName":"ervu.component.fileupload","className":"ErvuFileUpload","type":"TS"} + + + + removeFileButtonName + + "Удалить" + + + + selectFileButtonName + + "Выбрать" + + + + selectFileFieldText + + "Перетащите файл или выберите на компьютере" + + + + signFileUploadRef + + {"objectId":"409c0323-011e-4416-a631-f8f852299e1f","packageName":"ervu.component.fileupload","className":"ErvuFileUpload","type":"TS"} + + + + + + + ba24d307-0b91-4299-ba82-9d0b52384ff2 + 4333be98-584a-4d30-9427-4d4f7a8b7f7e + Текст + false + false + + + + cssClasses + + + + "mute" + + + + + + initialValue + + "Поддерживаемый формат файла - csv" + + + + + + + + +false + + + + + 9d1b5af1-0b8f-4b1b-b9a5-c2e6acf72d91 + b8373653-133f-43cd-a8c2-ba83f232d379 + Вертикальный контейнер + true + false + + + + + + + ba24d307-0b91-4299-ba82-9d0b52384ff2 + bc8596fe-1e46-4cf9-b306-43498f61909c + Текст + false + false + + + + cssClasses + + + + "subtitle" + + + + + + + + + + + initialValue + + "Ваша открепленная усиленная квалифицированная электронная подпись в формате sig" + + + + + + + + +false + + + + 5694e7c5-bbb5-4d23-be6c-7ad71b8ad38c + 409c0323-011e-4416-a631-f8f852299e1f + FileUploadV2 + false + false + + + + collectible + + false + + + + cssClasses + + + + "btn-main" + + + + + + extensionFilter + + + + + "sig" + + + + + + formType + + "FORM_9" + + + + maxFileSizeMb + + 5.0 + + + + maxFilesToUpload + + 1.0 + + + + removeFileButtonName + + "Удалить" + + + + selectFileButtonName + + "Выбрать" + + + + selectFileFieldText + + "Перетащите файл или выберите на компьютере" + + + + + + + ba24d307-0b91-4299-ba82-9d0b52384ff2 + 7f70dc30-8116-47da-ad8d-01cec26a60d7 + Текст + false + false + + + + cssClasses + + + + "mute" + + + + + + initialValue + + "Поддерживаемый формат файла - sig" + + + + + + + + +false + + + + 9d1b5af1-0b8f-4b1b-b9a5-c2e6acf72d91 + c4d501b1-7565-45c3-8722-d624126f6cfb + Вертикальный контейнер + true + true + + + + 9d1b5af1-0b8f-4b1b-b9a5-c2e6acf72d91 + c4d501b1-7565-45c3-8722-d624126f6cfb + Вертикальный контейнер + true + false + + + + + + + 8b755f7b-e52b-4800-830a-f01467cd5cbb + e958f2ee-e112-4bef-9c8a-40e2f8278ca9 + Check box + false + false + + + + + + + + ba24d307-0b91-4299-ba82-9d0b52384ff2 + f0794dfe-f0e9-446b-8029-c4ff9640f650 + Текст + false + false + + + + cssClasses + + + + "subtitle" + + + + + + + + + + + initialValue + + "Внимание: если файл подписан не руководителем организации, рекомендуется приложить МЧД" + + + + label + + null + + + + + + + + +false + + + + 5694e7c5-bbb5-4d23-be6c-7ad71b8ad38c + 1dc25461-a60e-456b-93cb-cc0f28dc347b + FileUploadV2 + false + false + false + + + + collectible + + false + + + + cssClasses + + + + "btn-main" + + + + + + extensionFilter + + + + + "xml" + + + + + + formType + + "FORM_9" + + + + maxFileSizeMb + + 5.0 + + + + maxFilesToUpload + + 1.0 + + + + removeFileButtonName + + "Удалить" + + + + selectFileButtonName + + "Выбрать" + + + + selectFileFieldText + + "Перетащите файл или выберите на компьютере" + + + + + + + ba24d307-0b91-4299-ba82-9d0b52384ff2 + 9b446462-16e5-4241-b409-0287dea92b3a + Текст + false + false + + + + cssClasses + + + + "mute" + + + + + + initialValue + + "Поддерживаемый формат файла - xml" + + + + + + + + false @@ -1493,73 +1979,35 @@ 7d338f47-6d12-4040-ba18-f31f520dce8d FileUploadV2 false - false - - - - collectible - - false - - - - cssClasses - - - - "btn-main" - - - - - - extensionFilter - - - - "csv" - - - - - - formType - - "FORM_9" - - - - maxFileSizeMb - - 5.0 - - - - maxFilesToUpload - - 1.0 - - - - removeFileButtonName - - "Удалить" - - - - selectFileButtonName - - "Выбрать" - - - - selectFileFieldText - - "Перетащите файл или выберите на компьютере" - - - - + true + + + ba24d307-0b91-4299-ba82-9d0b52384ff2 + 4333be98-584a-4d30-9427-4d4f7a8b7f7e + Текст + false + true + + + ba24d307-0b91-4299-ba82-9d0b52384ff2 + bc8596fe-1e46-4cf9-b306-43498f61909c + Текст + false + true + + + 5694e7c5-bbb5-4d23-be6c-7ad71b8ad38c + 409c0323-011e-4416-a631-f8f852299e1f + FileUploadV2 + false + true + + + ba24d307-0b91-4299-ba82-9d0b52384ff2 + 7f70dc30-8116-47da-ad8d-01cec26a60d7 + Текст + false + true 98594cec-0a9b-4cef-af09-e1b71cb2ad9e @@ -1596,6 +2044,24 @@ + + + + + behavior + + {"objectId":"409c0323-011e-4416-a631-f8f852299e1f","packageName":"ervu.component.fileupload","className":"ErvuFileUpload","type":"TS"} + + + + propertyName + + "fileAddedEvent" + + + + + @@ -1605,6 +2071,108 @@ conditions + + + + + _isGroupSelected + + false + + + + one + + + + conditionFirstPart + + + + objectValue + + + + behavior + + {"objectId":"7d338f47-6d12-4040-ba18-f31f520dce8d","packageName":"ervu.component.fileupload","className":"ErvuFileUpload","type":"TS"} + + + + method + + "getValue" + + + + + + + + + + operation + + "IS_NOT_EMPTY" + + + + + + + + + + + + + _isGroupSelected + + false + + + + one + + + + conditionFirstPart + + + + objectValue + + + + behavior + + {"objectId":"409c0323-011e-4416-a631-f8f852299e1f","packageName":"ervu.component.fileupload","className":"ErvuFileUpload","type":"TS"} + + + + method + + "getValue" + + + + + + + + + + operation + + "IS_NOT_EMPTY" + + + + + + + + @@ -1696,6 +2264,24 @@ + + + + + behavior + + {"objectId":"409c0323-011e-4416-a631-f8f852299e1f","packageName":"ervu.component.fileupload","className":"ErvuFileUpload","type":"TS"} + + + + propertyName + + "fileDeletedEvent" + + + + + @@ -1705,6 +2291,108 @@ conditions + + + + + _isGroupSelected + + false + + + + one + + + + conditionFirstPart + + + + objectValue + + + + behavior + + {"objectId":"7d338f47-6d12-4040-ba18-f31f520dce8d","packageName":"ervu.component.fileupload","className":"ErvuFileUpload","type":"TS"} + + + + method + + "getValue" + + + + + + + + + + operation + + "IS_EMPTY" + + + + + + + + + + + + + _isGroupSelected + + false + + + + one + + + + conditionFirstPart + + + + objectValue + + + + behavior + + {"objectId":"409c0323-011e-4416-a631-f8f852299e1f","packageName":"ervu.component.fileupload","className":"ErvuFileUpload","type":"TS"} + + + + method + + "getValue" + + + + + + + + + + operation + + "IS_EMPTY" + + + + + + + + @@ -1712,7 +2400,7 @@ logicalOperation - null + "OR" @@ -1850,39 +2538,6 @@ - - ba24d307-0b91-4299-ba82-9d0b52384ff2 - 4333be98-584a-4d30-9427-4d4f7a8b7f7e - Текст - false - false - - - - cssClasses - - - - "mute" - - - - - - initialValue - - "Поддерживаемый формат файла - csv" - - - - - - - - - false - - fd7e47b9-dce1-4d14-9f3a-580c79f59579 9d068751-8bbe-4fa0-b4b5-fa8e395fa019