SUPPORT-9339: Add sign verify

This commit is contained in:
Eduard Tihomirov 2025-09-04 10:19:01 +03:00
parent af52f7df8d
commit 22ffb8a866
11 changed files with 467 additions and 556 deletions

View file

@ -23,7 +23,7 @@ import org.springframework.kafka.core.ProducerFactory;
* @author Alexandr Shalaginov
*/
@Configuration
public class AvKafkaConfig {
public class FileKafkaConfig {
@Value("${kafka.hosts}")
private String kafkaUrl;
@Value("${kafka.auth_sec_proto}")
@ -38,7 +38,7 @@ public class AvKafkaConfig {
private String saslMechanism;
@Bean
public ProducerFactory<String, String> avProducerFactory() {
public ProducerFactory<String, String> fileProducerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
@ -58,7 +58,7 @@ public class AvKafkaConfig {
}
@Bean
public ConsumerFactory<String, String> avConsumerFactory() {
public ConsumerFactory<String, String> fileConsumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
@ -77,16 +77,16 @@ public class AvKafkaConfig {
return props;
}
@Bean("avContainerFactory")
@Bean("fileContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(avConsumerFactory());
factory.setConsumerFactory(fileConsumerFactory());
return factory;
}
@Bean("avTemplate")
@Bean("fileTemplate")
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(avProducerFactory());
return new KafkaTemplate<>(fileProducerFactory());
}
}

View file

@ -7,6 +7,7 @@ public enum FileStatusCode {
FILE_UPLOADED("01"),
FILE_INFECTED("02"),
FILE_CLEAN("03"),
FILE_ACCEPTED("04"),
FILE_NOT_CHECKED("11");
private final String code;

View file

@ -15,13 +15,14 @@ public class FileInfo {
private String departureDateTime;
private String timeZone;
private FileStatus fileStatus;
private String type;
public FileInfo() {
}
public FileInfo(String fileId, String fileUrl, String fileName, String filePatternCode,
String filePatternName, String fileSize, String departureDateTime, String timeZone,
FileStatus fileStatus) {
FileStatus fileStatus, String type) {
this.fileId = fileId;
this.fileUrl = fileUrl;
this.fileName = fileName;
@ -31,6 +32,7 @@ public class FileInfo {
this.departureDateTime = departureDateTime;
this.timeZone = timeZone;
this.fileStatus = fileStatus;
this.type = type;
}
public String getFileId() {
@ -69,6 +71,14 @@ public class FileInfo {
return fileStatus;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public void setFileStatus(FileStatus fileStatus) {
this.fileStatus = fileStatus;
}

View file

@ -11,9 +11,12 @@ import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import javax.xml.parsers.DocumentBuilderFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.Gson;
import ervu.client.fileupload.WebDavClient;
import ervu.model.fileupload.*;
import org.apache.http.HttpEntity;
@ -34,12 +37,18 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import ru.micord.ervu.audit.service.AuditService;
import ru.micord.ervu.exception.JsonParsingException;
import ru.micord.ervu.kafka.service.ReplyingKafkaService;
import ru.micord.ervu.security.esia.config.EsiaConfig;
import ru.micord.ervu.security.esia.exception.EsiaException;
import ru.micord.ervu.security.esia.model.EmployeeModel;
@ -53,6 +62,10 @@ import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
import ru.micord.ervu.service.InteractionService;
import ru.micord.ervu.util.DateUtils;
import ru.cg.webbpm.modules.core.runtime.api.LocalizedException;
import ru.cg.webbpm.modules.core.runtime.api.MessageBundleUtils;
import static ervu.enums.FileStatusCode.FILE_ACCEPTED;
import static ervu.enums.FileStatusCode.FILE_CLEAN;
import static ervu.enums.FileStatusCode.FILE_INFECTED;
import static ervu.enums.FileStatusCode.FILE_NOT_CHECKED;
@ -65,8 +78,12 @@ import static ru.micord.ervu.util.StringUtils.convertToFio;
@Service
public class EmployeeInfoFileUploadService {
private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeInfoFileUploadService.class);
private static final String DOCUMENT = "DOCUMENT";
private static final MessageSourceAccessor MESSAGE_SOURCE = MessageBundleUtils.createAccessor(
"messages/common_errors_messages");
private final WebDavClient webDavClient;
private final EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService;
private final ReplyingKafkaService replyingKafkaService;
private final KafkaTemplate<String, String> kafkaTemplate;
private final InteractionService interactionService;
private final UlDataService ulDataService;
@ -74,31 +91,43 @@ public class EmployeeInfoFileUploadService {
private final ObjectMapper objectMapper;
private final EsiaConfig esiaConfig;
@Value("${av.kafka.message.topic.name}")
private String kafkaTopicName;
@Value("${kafka.clear.s3}")
private String kafkaClearS3Topic;
@Value("${kafka.download.request}")
private String kafkaDownloadRequestTopic;
@Value("${av.kafka.download.request}")
private String kafkaRequestTopic;
@Value("${av.kafka.download.response}")
private String kafkaResponseTopic;
public EmployeeInfoFileUploadService(
WebDavClient webDavClient,
EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService,
@Qualifier("avTemplate") KafkaTemplate<String, String> kafkaTemplate,
ReplyingKafkaService replyingKafkaService,
InteractionService interactionService,
UlDataService ulDataService,
AuditService auditService,
ObjectMapper objectMapper,
EsiaConfig esiaConfig) {
EsiaConfig esiaConfig,
@Qualifier("fileTemplate") KafkaTemplate<String, String> kafkaTemplate) {
this.webDavClient = webDavClient;
this.employeeInfoKafkaMessageService = employeeInfoKafkaMessageService;
this.kafkaTemplate = kafkaTemplate;
this.replyingKafkaService = replyingKafkaService;
this.interactionService = interactionService;
this.ulDataService = ulDataService;
this.auditService = auditService;
this.objectMapper = objectMapper;
this.esiaConfig = esiaConfig;
this.kafkaTemplate = kafkaTemplate;
}
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);
@ -106,182 +135,245 @@ public class EmployeeInfoFileUploadService {
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();
String ervuId = userIdsPair.getErvuId();
String accessToken = EsiaAuthInfoStore.getAccessToken(esiaUserId);
EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken);
PersonModel personModel = employeeModel.getPerson();
EmployeeModel chiefModel = ulDataService.getChiefEmployeeModel(accessToken);
VerifyDocumentSignResponse verifyDocumentSignResponse = validateSign(multipartFile, signFile);
FileStatus fileStatus = new FileStatus();
String fio = convertToFio(personModel.getFirstName(), personModel.getMiddleName(),
personModel.getLastName()
);
UploadOrgInfo uploadOrgInfo = employeeInfoKafkaMessageService.getOrgInfo(accessToken, ervuId,
esiaUserId, personModel
);
FileInfo fileInfo = employeeInfoKafkaMessageService.getFileInfo(fileId, null, fileName,
employeeInfoFileFormType, departureDateTime, offset, null, fileSize
EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(formType);
FileInfo fileInfo = createFileInfo(multipartFile, employeeInfoFileFormType,
departureDateTime, offset, DOCUMENT
);
FileInfo signFileInfo = employeeInfoKafkaMessageService.getFileInfo(signFileId, null,
signFileName, employeeInfoFileFormType, departureDateTime, offset, null, signFileSize
FileInfo signFileInfo = createFileInfo(signFile, employeeInfoFileFormType,
departureDateTime, offset, "SIGNATURE"
);
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());
FileInfo mchdFileInfo = mchdFile != null ?
createFileInfo(mchdFile, employeeInfoFileFormType, departureDateTime,
offset, "MCHD"
) : null;
FileStatus fileStatus = new FileStatus();
Map<String, String> uploadResults = uploadFiles(multipartFile, signFile, mchdFile);
String fileUploadUrl = uploadResults.get("fileUrl");
String signFileUploadUrl = uploadResults.get("signFile");
String mchdFileUploadUrl = uploadResults.get("mchdFile");
boolean uploadSuccess = checkUploadSuccess(fileUploadUrl, signFileUploadUrl, mchdFileUploadUrl,
mchdFile != null
);
fileStatus.setStatus(uploadSuccess ? "Загрузка" : "Невозможно проверить файл ЛК РП");
interactionService.setStatus(fileInfo.getFileId(), fileStatus.getStatus(),
multipartFile.getOriginalFilename(), employeeInfoFileFormType.getFilePatternCode(),
Timestamp.valueOf(now), fio, ervuId
);
fileInfo.setFileStatus(fileStatus);
signFileInfo.setFileStatus(fileStatus);
if (mchdFileInfo != null) {
mchdFileInfo.setFileStatus(fileStatus);
}
if (!uploadSuccess) {
handleUploadFailure(fileStatus, uploadOrgInfo, fileInfo, signFileInfo, mchdFileInfo,
multipartFile.getOriginalFilename(), signFile.getOriginalFilename(),
mchdFile != null ? mchdFile.getOriginalFilename() : null
);
return false;
}
fileInfo.setFileUrl(fileUploadUrl);
signFileInfo.setFileUrl(signFileUploadUrl);
if (mchdFileInfo != null) {
mchdFileInfo.setFileUrl(mchdFileUploadUrl);
}
FileInfo[] fileInfos = mchdFileInfo != null ?
new FileInfo[] {fileInfo, signFileInfo, mchdFileInfo} :
new FileInfo[] {fileInfo, signFileInfo};
String response = sendKafkaMessage(uploadOrgInfo, fileInfos, fileStatus);
DownloadResponse downloadResponse;
try {
downloadResponse = processMessageFromAv(response);
}
catch (JsonProcessingException e) {
fileStatus.setCode(FILE_NOT_CHECKED.getCode());
fileStatus.setStatus("Невозможно проверить файл ЛК РП");
interactionService.updateStatus(fileInfo.getFileId(), fileInfo.getFileStatus().getStatus(),
uploadOrgInfo.getOrgId()
);
auditService.processUploadEvent(uploadOrgInfo, fileInfos);
throw new RuntimeException(e);
}
VerifyDocumentSignResponse verifyDocumentSignResponse;
try {
verifyDocumentSignResponse = validateSign(multipartFile, signFile);
}
catch (Exception e) {
fileStatus.setCode(FILE_NOT_CHECKED.getCode());
fileStatus.setStatus("Некорректная ЭП");
Arrays.stream(downloadResponse.filesInfo())
.forEach(fileInfo1 -> fileInfo1.setFileStatus(fileStatus));
interactionService.updateStatus(fileInfo.getFileId(), fileInfo.getFileStatus().getStatus(),
uploadOrgInfo.getOrgId()
);
auditService.processUploadEvent(uploadOrgInfo, new FileInfo[] {fileInfo, signFileInfo});
clearS3(response);
throw e;
}
return validateSignerAndMchd(verifyDocumentSignResponse, chiefModel, uploadOrgInfo,
mchdFile, accessToken, fileStatus, fileInfo, signFileInfo, mchdFileInfo,
now, fio, ervuId, employeeInfoFileFormType, response
);
}
private FileInfo createFileInfo(MultipartFile file, EmployeeInfoFileFormType formType,
String departureDateTime, String offset, String fileType) {
String fileId = UUID.randomUUID().toString();
return employeeInfoKafkaMessageService.getFileInfo(fileId, null, file.getOriginalFilename(),
formType, departureDateTime, offset, null, file.getSize(), fileType
);
}
private Map<String, String> uploadFiles(MultipartFile multipartFile, MultipartFile signFile,
MultipartFile mchdFile) {
Map<String, MultipartFile> filesToUpload = new HashMap<>();
filesToUpload.put("fileUrl", multipartFile);
filesToUpload.put("signFile", signFile);
if (mchdFile != null) {
filesToUpload.put("mchdFile", mchdFile);
}
return this.webDavClient.uploadFiles(filesToUpload);
}
private boolean checkUploadSuccess(String fileUploadUrl, String signFileUploadUrl,
String mchdFileUploadUrl, boolean hasMchdFile) {
if (hasMchdFile) {
return fileUploadUrl != null && signFileUploadUrl != null && mchdFileUploadUrl != null;
}
else {
return fileUploadUrl != null && signFileUploadUrl != null;
}
}
private void handleUploadFailure(FileStatus fileStatus, UploadOrgInfo uploadOrgInfo,
FileInfo fileInfo, FileInfo signFileInfo, FileInfo mchdFileInfo,
String fileName, String signFileName, String mchdFileName) {
LOGGER.error("Failed to upload files: {}, {}, {}", fileName, signFileName, mchdFileName);
fileStatus.setCode(FILE_NOT_CHECKED.getCode());
fileStatus.setDescription(
"Невозможно проверить файл по причине недоступности или ошибки в работе антивируса");
FileInfo[] fileInfos = mchdFileInfo != null ?
new FileInfo[] {fileInfo, signFileInfo, mchdFileInfo} :
new FileInfo[] {fileInfo, signFileInfo};
auditService.processUploadEvent(uploadOrgInfo, fileInfos);
}
private String sendKafkaMessage(UploadOrgInfo uploadOrgInfo, FileInfo[] fileInfos,
FileStatus fileStatus) {
EmployeeInfoKafkaMessage kafkaMessage = employeeInfoKafkaMessageService.getKafkaMessage(
uploadOrgInfo, fileInfos);
fileStatus.setCode(FILE_UPLOADED.getCode());
fileStatus.setDescription("Файл принят до проверки на вирусы");
String jsonMessage = getJsonKafkaMessage(kafkaMessage);
return replyingKafkaService.sendMessageAndGetReply(kafkaRequestTopic,
kafkaResponseTopic, jsonMessage
);
}
private boolean validateSignerAndMchd(VerifyDocumentSignResponse verifyDocumentSignResponse,
EmployeeModel chiefModel, UploadOrgInfo uploadOrgInfo, MultipartFile mchdFile,
String accessToken, FileStatus fileStatus, FileInfo fileInfo, FileInfo signFileInfo,
FileInfo mchdFileInfo, LocalDateTime now, String fio, String ervuId,
EmployeeInfoFileFormType formType, String response) {
String signerInfo = verifyDocumentSignResponse.getSignerInfo();
Map<String, String> 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<String, String> 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);
}
boolean isSignerValid = signerInfoMap.get("SN").equalsIgnoreCase(chiefLastName) &&
signerInfoMap.get("G")
.equalsIgnoreCase(chiefFirstName + " " + chiefMiddleName) &&
signerInfoMap.get("O").equalsIgnoreCase(uploadOrgInfo.getOrgName());
if (isSignerValid) {
sendMessage(response);
return true;
}
else {
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 (mchdFile == null) {
handleMchdValidationError(fileStatus, uploadOrgInfo, fileInfo, signFileInfo, null,
now, fio, ervuId, formType, response
);
if (validateMchd(mchdFile, accessToken)) {
Map<String, String> 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();
}
throw new LocalizedException("mchd_null", MESSAGE_SOURCE);
}
try {
validateMchd(mchdFile, accessToken, signerInfoMap.get("SN") + " " + signerInfoMap.get("G"),
chiefFirstName, chiefLastName, chiefMiddleName
);
sendMessage(response);
return true;
}
catch (Exception e) {
handleMchdValidationError(fileStatus, uploadOrgInfo, fileInfo, signFileInfo, mchdFileInfo,
now, fio, ervuId, formType, response
);
throw e;
}
}
private void handleMchdValidationError(FileStatus fileStatus, UploadOrgInfo uploadOrgInfo,
FileInfo fileInfo, FileInfo signFileInfo, FileInfo mchdFileInfo, LocalDateTime now,
String fio, String ervuId, EmployeeInfoFileFormType formType, String response) {
fileStatus.setCode(FILE_NOT_CHECKED.getCode());
fileStatus.setStatus("Некорректная МЧД");
interactionService.setStatus(fileInfo.getFileId(), fileStatus.getStatus(),
fileInfo.getFileName(), formType.getFilePatternCode(), Timestamp.valueOf(now),
fio, ervuId
);
FileInfo[] fileInfos = mchdFileInfo != null ?
new FileInfo[] {fileInfo, signFileInfo, mchdFileInfo} :
new FileInfo[] {fileInfo, signFileInfo};
auditService.processUploadEvent(uploadOrgInfo, fileInfos);
clearS3(response);
}
private boolean isValidCsvWithSig(MultipartFile file, MultipartFile signFile) {
@ -325,22 +417,6 @@ public class EmployeeInfoFileUploadService {
}
}
private boolean sendMessage(String message) {
ProducerRecord<String, String> record = new ProducerRecord<>(this.kafkaTopicName, message);
record.headers()
.add("messageId", UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
try {
this.kafkaTemplate.send(record).get();
LOGGER.debug("Successfully sent record: {}", record);
return true;
}
catch (Exception exception) {
LOGGER.error("Failed to send message", exception);
return false;
}
}
private String getJsonKafkaMessage(EmployeeInfoKafkaMessage employeeInfoKafkaMessage) {
ObjectMapper mapper = new ObjectMapper();
try {
@ -352,25 +428,22 @@ public class EmployeeInfoFileUploadService {
}
}
@KafkaListener(id = "${av.kafka.group.id}", topics = "${av.kafka.download.response}",
containerFactory = "avContainerFactory")
@KafkaListener(id = "${file.kafka.group.id}", topics = "${kafka.download.response}",
containerFactory = "fileContainerFactory")
public void listenKafka(String kafkaMessage) {
ObjectMapper mapper = new ObjectMapper();
try {
DownloadResponse downloadResponse = mapper.readValue(kafkaMessage, DownloadResponse.class);
FileInfo fileInfo = downloadResponse.filesInfo()[0];
FileInfo fileInfo = Arrays.stream(downloadResponse.filesInfo())
.filter(fileInfo1 -> fileInfo1.getType().equals(DOCUMENT))
.findFirst()
.get();
String statusCode = fileInfo.getFileStatus().getCode();
if (Arrays.asList(FILE_INFECTED.getCode(), FILE_CLEAN.getCode()).contains(statusCode)) {
if (FILE_ACCEPTED.getCode().equals(statusCode)) {
interactionService.delete(fileInfo.getFileId(), downloadResponse.orgInfo().getOrgId());
}
else if (statusCode.equals(FILE_NOT_CHECKED.getCode())) {
auditService.processUploadEvent(downloadResponse.orgInfo(), downloadResponse.filesInfo());
interactionService.updateStatus(fileInfo.getFileId(), fileInfo.getFileStatus().getStatus(),
downloadResponse.orgInfo().getOrgId()
);
}
}
catch (JsonProcessingException e) {
throw new JsonParsingException(String.format("Fail get json from: %s", kafkaMessage), e);
@ -392,26 +465,61 @@ public class EmployeeInfoFileUploadService {
try (CloseableHttpResponse response = httpClient.execute(upload)) {
int statusCode = response.getStatusLine().getStatusCode();
String body = EntityUtils.toString(response.getEntity());
if (statusCode == 401) {
throw new LocalizedException("file_sign_validate", MESSAGE_SOURCE);
}
String errorCode = objectMapper.readTree(body)
.get("error_code")
.asText();
if (errorCode.equals("CERT_TRUST_REVOCATION_STATUS_UNKNOWN")) {
throw new LocalizedException("crl_certificate_expired", MESSAGE_SOURCE);
}
if (statusCode != 200) {
throw new RuntimeException(statusCode + " " + body);
throw new RuntimeException("Unknown error in verify module. Error code " + errorCode);
}
return objectMapper.readValue(body, VerifyDocumentSignResponse.class);
}
}
catch (Exception e) {
throw new RuntimeException(e);
catch (IOException e) {
throw new RuntimeException("Failed to process sign module response ", e);
}
}
private boolean validateMchd(MultipartFile mchdFile, String accessToken) {
private void validateMchd(MultipartFile mchdFile, String accessToken, String agentFio,
String chiefFirstName, String chiefLastName, String chiefMiddleName) {
String mchdGuid;
try {
String mchdGuid = getMchdGuid(mchdFile);
MchdInfoModel mchdInfoModel = ulDataService.getMchdInfo(mchdGuid, accessToken);
mchdInfoModel.
mchdGuid = getMchdGuid(mchdFile);
}
catch (Exception e) {
throw new EsiaException(e);
throw new LocalizedException("mchd_cant_parse", MESSAGE_SOURCE);
}
MchdInfoModel mchdInfoModel = ulDataService.getMchdInfoModel(mchdGuid, accessToken);
if (!mchdInfoModel.getStatus().equals("A")) {
throw new LocalizedException("mchd_expired", MESSAGE_SOURCE);
}
boolean validAgent = mchdInfoModel.getAgents()
.getElements()
.stream()
.anyMatch(agent1 -> agentFio.equalsIgnoreCase(
agent1.getPerson().getLastName() + " " + agent1.getPerson().getFirstName() + " "
+ agent1.getPerson().getMiddleName()));
if (!validAgent) {
throw new LocalizedException("mchd_validate_agent", MESSAGE_SOURCE);
}
while (mchdInfoModel.getParentGuid() != null) {
mchdInfoModel = ulDataService.getMchdInfoModel(mchdInfoModel.getParentGuid(), accessToken);
if (!mchdInfoModel.getStatus().equals("A")) {
throw new LocalizedException("mchd_tree_expired", MESSAGE_SOURCE);
}
}
MchdInfoModel.Element principal = mchdInfoModel.getPrincipals().getElements().get(0);
MchdInfoModel.Person chief = principal.getOrganization().getChief();
boolean principalFioEquals = chief.getFirstName().equalsIgnoreCase(chiefFirstName) &&
chief.getLastName().equalsIgnoreCase(chiefLastName) &&
chief.getMiddleName().equalsIgnoreCase(chiefMiddleName);
if (!principalFioEquals) {
throw new LocalizedException("mchd_validate_principal", MESSAGE_SOURCE);
}
}
@ -428,14 +536,67 @@ public class EmployeeInfoFileUploadService {
return map;
}
private String getMchdGuid(MultipartFile mcnhdFile) {
try {
return "asdasdasd";
private String getMchdGuid(MultipartFile mchdFile) throws Exception {
Document doc = DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse(mchdFile.getInputStream());
doc.getDocumentElement().normalize();
Node node = doc.getElementsByTagNameNS("*", "СвДов").item(0);
if (node != null && node.getAttributes().getNamedItem("НомДовер") != null) {
return node.getAttributes().getNamedItem("НомДовер").getNodeValue();
}
catch (Exception e) {
throw new EsiaException(e);
return null;
}
private boolean sendMessage(String message) {
ProducerRecord<String, String> record = new ProducerRecord<>(this.kafkaDownloadRequestTopic, message);
record.headers()
.add("messageId", UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
try {
this.kafkaTemplate.send(record).get();
LOGGER.debug("Successfully sent record: {}", record);
return true;
}
catch (Exception exception) {
LOGGER.error("Failed to send message", exception);
return false;
}
}
private void clearS3(String message) {
ProducerRecord<String, String> record = new ProducerRecord<>(this.kafkaClearS3Topic, message);
record.headers()
.add("messageId", UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
try {
this.kafkaTemplate.send(record).get();
}
catch (Exception exception) {
LOGGER.error("Failed to clear s3", exception);
}
}
private DownloadResponse processMessageFromAv(String response) throws JsonProcessingException {
DownloadResponse downloadResponse = objectMapper.readValue(response, DownloadResponse.class);
FileInfo avFile = Arrays.stream(downloadResponse.filesInfo())
.filter(fileInfo1 -> fileInfo1.getType().equals(DOCUMENT))
.findAny()
.get();
FileStatus fileStatus1 = avFile.getFileStatus();
if (fileStatus1.getCode().equals(FILE_INFECTED.getCode())) {
interactionService.updateStatus(avFile.getFileId(), avFile.getFileStatus().getStatus(),
downloadResponse.orgInfo().getOrgId());
sendMessage(response);
throw new LocalizedException("av_file_infected", MESSAGE_SOURCE);
}
else if (fileStatus1.getCode().equals(FILE_NOT_CHECKED.getCode())) {
interactionService.updateStatus(avFile.getFileId(), avFile.getFileStatus().getStatus(),
downloadResponse.orgInfo().getOrgId());
auditService.processUploadEvent(downloadResponse.orgInfo(), downloadResponse.filesInfo());
throw new RuntimeException("File not checked: " + avFile.getFileName());
}
return downloadResponse;
}
}

View file

@ -26,7 +26,7 @@ public class EmployeeInfoKafkaMessageService {
public EmployeeInfoKafkaMessage getKafkaMessage(String fileId, String fileUrl, String fileName,
EmployeeInfoFileFormType formType, String departureDateTime, String accessToken,
String offset, FileStatus fileStatus, String ervuId, String prnOid,
PersonModel personModel, long fileSize) {
PersonModel personModel, long fileSize, String type) {
return new EmployeeInfoKafkaMessage(
getOrgInfo(accessToken, ervuId, prnOid, personModel),
new FileInfo[] {
@ -38,7 +38,8 @@ public class EmployeeInfoKafkaMessageService {
departureDateTime,
offset,
fileStatus,
fileSize
fileSize,
type
),
}
);
@ -50,7 +51,7 @@ public class EmployeeInfoKafkaMessageService {
public FileInfo getFileInfo(String fileId, String fileUrl, String fileName,
EmployeeInfoFileFormType formType, String departureDateTime, String offset,
FileStatus fileStatus, long fileSize) {
FileStatus fileStatus, long fileSize, String type) {
return new FileInfo(
fileId,
fileUrl,
@ -60,7 +61,8 @@ public class EmployeeInfoKafkaMessageService {
String.valueOf(fileSize),
departureDateTime,
offset,
fileStatus
fileStatus,
type
);
}

View file

@ -12,10 +12,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.core.*;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import org.springframework.kafka.requestreply.CorrelationKey;
import org.springframework.kafka.requestreply.ReplyingKafkaTemplate;
@ -52,6 +49,8 @@ public class ReplyingKafkaConfig {
private String password;
@Value("${kafka.auth_sasl_mech}")
private String saslMechanism;
@Value("${av.kafka.download.response}")
private String avReplyTopic;
@Bean("ervuProducerFactory")
public ProducerFactory<String, String> producerFactory() {
@ -61,7 +60,7 @@ public class ReplyingKafkaConfig {
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
configProps.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\""
+ username + "\" password=\"" + password + "\";");
+ username + "\" password=\"" + password + "\";");
configProps.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
return new DefaultKafkaProducerFactory<>(configProps);
}
@ -91,8 +90,11 @@ public class ReplyingKafkaConfig {
@Bean
public ConcurrentMessageListenerContainer<String, String> replyContainer(
@Qualifier("ervuContainerFactory") ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory) {
return kafkaListenerContainerFactory.createContainer(orgReplyTopic, excerptReplyTopic, journalReplyTopic);
@Qualifier("ervuContainerFactory")
ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory) {
return kafkaListenerContainerFactory.createContainer(orgReplyTopic, excerptReplyTopic,
journalReplyTopic, avReplyTopic
);
}
@Bean

View file

@ -12,26 +12,11 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class MchdInfoModel implements Serializable {
private static final long serialVersionUID = 1L;
private List<String> 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<String> getStateFacts() {
return stateFacts;
}
public void setStateFacts(List<String> stateFacts) {
this.stateFacts = stateFacts;
}
private String parentGuid;
private Member principals;
private Member agents;
public String getGuid() {
return guid;
@ -41,14 +26,6 @@ public class MchdInfoModel implements Serializable {
this.guid = guid;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getStatus() {
return status;
}
@ -57,98 +34,48 @@ public class MchdInfoModel implements Serializable {
this.status = status;
}
public String getIssuedOn() {
return issuedOn;
public String getParentGuid() {
return parentGuid;
}
public void setIssuedOn(String issuedOn) {
this.issuedOn = issuedOn;
public void setParentGuid(String parentGuid) {
this.parentGuid = parentGuid;
}
public String getExpiredOn() {
return expiredOn;
}
public void setExpiredOn(String expiredOn) {
this.expiredOn = expiredOn;
}
public Principals getPrincipals() {
public Member getPrincipals() {
return principals;
}
public void setPrincipals(Principals principals) {
public void setPrincipals(Member principals) {
this.principals = principals;
}
public Agents getAgents() {
public Member getAgents() {
return agents;
}
public void setAgents(Agents agents) {
public void setAgents(Member 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<String> stateFacts;
private int size;
private List<PrincipalElement> elements;
public static class Member implements Serializable {
private List<Element> elements;
public List<String> getStateFacts() {
return stateFacts;
}
public void setStateFacts(List<String> stateFacts) {
this.stateFacts = stateFacts;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public List<PrincipalElement> getElements() {
public List<Element> getElements() {
return elements;
}
public void setElements(List<PrincipalElement> elements) {
public void setElements(List<Element> elements) {
this.elements = elements;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class PrincipalElement implements Serializable {
private List<String> stateFacts;
public static class Element implements Serializable {
private Person person;
private Organization organization;
public List<String> getStateFacts() {
return stateFacts;
}
public void setStateFacts(List<String> stateFacts) {
this.stateFacts = stateFacts;
}
public Organization getOrganization() {
return organization;
}
@ -156,51 +83,6 @@ public class MchdInfoModel implements Serializable {
public void setOrganization(Organization organization) {
this.organization = organization;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Agents implements Serializable {
private List<String> stateFacts;
private int size;
private List<AgentElement> elements;
public List<String> getStateFacts() {
return stateFacts;
}
public void setStateFacts(List<String> stateFacts) {
this.stateFacts = stateFacts;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public List<AgentElement> getElements() {
return elements;
}
public void setElements(List<AgentElement> elements) {
this.elements = elements;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class AgentElement implements Serializable {
private List<String> stateFacts;
private Person person;
public List<String> getStateFacts() {
return stateFacts;
}
public void setStateFacts(List<String> stateFacts) {
this.stateFacts = stateFacts;
}
public Person getPerson() {
return person;
@ -211,171 +93,10 @@ public class MchdInfoModel implements Serializable {
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Systems implements Serializable {
private List<String> stateFacts;
private int size;
private List<SystemElement> elements;
public List<String> getStateFacts() {
return stateFacts;
}
public void setStateFacts(List<String> stateFacts) {
this.stateFacts = stateFacts;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public List<SystemElement> getElements() {
return elements;
}
public void setElements(List<SystemElement> 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<String> stateFacts;
private int size;
private List<RevocationElement> elements;
public List<String> getStateFacts() {
return stateFacts;
}
public void setStateFacts(List<String> stateFacts) {
this.stateFacts = stateFacts;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public List<RevocationElement> getElements() {
return elements;
}
public void setElements(List<RevocationElement> 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;
}
private Person chief;
public String getName() {
return name;
@ -385,55 +106,43 @@ public class MchdInfoModel implements Serializable {
this.name = name;
}
public String getInn() {
return inn;
public Person getChief() {
return chief;
}
public void setInn(String inn) {
this.inn = inn;
}
public String getKpp() {
return kpp;
}
public void setKpp(String kpp) {
this.kpp = kpp;
public void setChief(Person chief) {
this.chief = chief;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Person implements Serializable {
private String snils;
private String firstName;
private String lastName;
private String middleName;
public String getSnils() {
return snils;
public String getFirstName() {
return firstName;
}
public void setSnils(String snils) {
this.snils = snils;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Signatory implements Serializable {
private List<String> stateFacts;
private Person person;
public List<String> getStateFacts() {
return stateFacts;
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setStateFacts(List<String> stateFacts) {
this.stateFacts = stateFacts;
public String getLastName() {
return lastName;
}
public Person getPerson() {
return person;
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setPerson(Person person) {
this.person = person;
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
}
}

View file

@ -22,4 +22,6 @@ public interface UlDataService {
String getAllUserRoles(String accessToken);
EsiaHeader readHeader(String accessToken);
MchdInfoModel getMchdInfoModel(String guid, String accessToken);
}

View file

@ -17,12 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import ru.micord.ervu.security.esia.exception.EsiaException;
import ru.micord.ervu.security.esia.model.BrhsModel;
import ru.micord.ervu.security.esia.model.EmployeeModel;
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
import ru.micord.ervu.security.esia.model.EsiaHeader;
import ru.micord.ervu.security.esia.model.OrganizationModel;
import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.esia.model.*;
/**
* @author Eduard Tihomirov
@ -282,4 +277,25 @@ public class UlDataServiceImpl implements UlDataService {
throw new EsiaException(e);
}
}
public MchdInfoModel getMchdInfoModel(String guid, String accessToken) {
try {
String url = esiaConfig.getEsiaBaseUri() + "/poa-registry/api/public/v1/poa/" + guid;
HttpRequest getReq = HttpRequest.newBuilder(URI.create(url))
.header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.header("Authorization", "Bearer ".concat(accessToken))
.GET()
.timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout()))
.build();
HttpResponse<String> getResp = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout()))
.build()
.send(getReq, HttpResponse.BodyHandlers.ofString());
errorHandler(getResp);
return objectMapper.readValue(getResp.body(), MchdInfoModel.class);
}
catch (Exception e) {
throw new EsiaException(e);
}
}
}

View file

@ -2,3 +2,11 @@ kafka_reply_timeout=Превышено время ожидания ответа
access_denied=Доступ запрещен. Пользователь должен быть включен в группу "Сотрудник, ответственный за военно-учетную работу" в ЕСИА
login_attempts_exceeded=Слишком большое количество попыток авторизоваться в ЕСИА за короткий промежуток времени. Рекомендуем почистить cookie и cash браузера, после повторить авторизацию.
crl_certificate_expired=Превышено время ожидания ответа из ЕСИА
file_sign_validate=Ошибка проверки файлов. Некорректная электронная подпись
mchd_validate_agent=Ошибка проверки файлов. Некорректная машиночитаемая доверенность. Представитель не совпадает с подписантом
mchd_null=Ошибка проверки файлов. Отсутствует машиночитаемая доверенность. Подписант не является руководителем организации
mchd_expired=Ошибка проверки файлов. Недействующая машиночитаемая доверенность.
mchd_tree_expired=Ошибка проверки файлов. Одна из родительский доверенностей недействующая.
mchd_validate_principal=Ошибка проверки файлов. Некорректная машиночитаемая доверенность. Доверитель не совпадает с руководителем организации
av_file_infected=Ошибка проверки файлов. Файлы заражены вирусом
mchd_cant_parse=Ошибка проверки файлов. Некорректный формат машиночитаемой доверенности

View file

@ -88,7 +88,7 @@
<property name="db.journal.excluded.statuses" value="Направлено в ЕРВУ,Получен ЕРВУ"/>
<property name="ervu.kafka.excerpt.reply.topic" value="ervu.lkrp.excerpt.response"/>
<property name="ervu.kafka.excerpt.request.topic" value="ervu.lkrp.excerpt.request"/>
<property name="av.kafka.group.id" value="1"/>
<property name="file.kafka.group.id" value="1"/>
<property name="av.kafka.download.response" value="ervu.lkrp.av-fileupload-status"/>
<property name="sign.verify.url" value="https://ervu-sign-dev.k8s.micord.ru/verify"/>
<property name="esia.auth.info.clear.cron" value="0 0 */1 * * *"/>