Merge branch 'hotfix/1.9.9' into feature/SUPPORT-8643_error_code
# Conflicts: # frontend/src/ts/modules/security/guard/auth.guard.ts
This commit is contained in:
commit
2355a004ac
202 changed files with 10426 additions and 8406 deletions
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ru.micord.ervu.lkrp</groupId>
|
||||
<artifactId>ul</artifactId>
|
||||
<version>1.9.8-SNAPSHOT</version>
|
||||
<version>1.9.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>ru.micord.ervu.lkrp.ul</groupId>
|
||||
<artifactId>backend</artifactId>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import org.springframework.retry.annotation.Retryable;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.micord.ervu.security.esia.config.EsiaConfig;
|
||||
import ru.micord.ervu.util.UrlUtils;
|
||||
|
||||
/**
|
||||
* @author Alexandr Shalaginov
|
||||
|
|
@ -186,14 +187,17 @@ public class WebDavClient {
|
|||
HttpResponse<InputStream> response = httpClient.send(httpRequest,
|
||||
HttpResponse.BodyHandlers.ofInputStream()
|
||||
);
|
||||
|
||||
if (response.statusCode() == 200) {
|
||||
long contentLength = response.headers()
|
||||
.firstValueAsLong(HttpHeaders.CONTENT_LENGTH)
|
||||
.orElse(0);
|
||||
InputStreamResource resource = new InputStreamResource(response.body());
|
||||
String encodedFilename = URLEncoder.encode(getFilenameFromUrl(url), StandardCharsets.UTF_8);
|
||||
String encodedFilename = URLEncoder.encode(UrlUtils.extractFileNameFromUrl(url), StandardCharsets.UTF_8);
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename*=UTF-8''" + encodedFilename
|
||||
)
|
||||
.contentLength(contentLength)
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(resource);
|
||||
}
|
||||
|
|
@ -207,10 +211,6 @@ public class WebDavClient {
|
|||
}
|
||||
}
|
||||
|
||||
private String getFilenameFromUrl(String url) {
|
||||
String path = URI.create(url).getPath();
|
||||
return path.substring(path.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
@Retryable(value = {IOException.class}, backoff = @Backoff(delayExpression = "${webdav.retry.delay:500}"))
|
||||
public void deleteFilesOlderThan(long seconds, String url, String... extensions) throws IOException {
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ package ervu.model.fileupload;
|
|||
/**
|
||||
* @author r.latypov
|
||||
*/
|
||||
public record DownloadResponse(OrgInfo orgInfo, FileInfo fileInfo) {
|
||||
public record DownloadResponse(UploadOrgInfo orgInfo, FileInfo fileInfo) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ import java.util.Objects;
|
|||
* @author Alexandr Shalaginov
|
||||
*/
|
||||
public class EmployeeInfoKafkaMessage {
|
||||
private final OrgInfo orgInfo;
|
||||
private final UploadOrgInfo orgInfo;
|
||||
private final FileInfo fileInfo;
|
||||
|
||||
public EmployeeInfoKafkaMessage(OrgInfo orgInfo, FileInfo fileInfo) {
|
||||
public EmployeeInfoKafkaMessage(UploadOrgInfo orgInfo, FileInfo fileInfo) {
|
||||
this.orgInfo = orgInfo;
|
||||
this.fileInfo = fileInfo;
|
||||
}
|
||||
|
||||
public OrgInfo getOrgInfo() {
|
||||
public UploadOrgInfo getOrgInfo() {
|
||||
return orgInfo;
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ public class EmployeeInfoKafkaMessage {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "KafkaMessage{" +
|
||||
"orgInfo=" + orgInfo +
|
||||
"uploadOrgInfo=" + orgInfo +
|
||||
", fileInfo=" + fileInfo +
|
||||
'}';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ public class FileInfo {
|
|||
private String fileName;
|
||||
private String filePatternCode;
|
||||
private String filePatternName;
|
||||
private String fileSize;
|
||||
private String departureDateTime;
|
||||
private String timeZone;
|
||||
private FileStatus fileStatus;
|
||||
|
|
@ -19,12 +20,14 @@ public class FileInfo {
|
|||
}
|
||||
|
||||
public FileInfo(String fileId, String fileUrl, String fileName, String filePatternCode,
|
||||
String filePatternName, String departureDateTime, String timeZone, FileStatus fileStatus) {
|
||||
String filePatternName, String fileSize, String departureDateTime, String timeZone,
|
||||
FileStatus fileStatus) {
|
||||
this.fileId = fileId;
|
||||
this.fileUrl = fileUrl;
|
||||
this.fileName = fileName;
|
||||
this.filePatternCode = filePatternCode;
|
||||
this.filePatternName = filePatternName;
|
||||
this.fileSize = fileSize;
|
||||
this.departureDateTime = departureDateTime;
|
||||
this.timeZone = timeZone;
|
||||
this.fileStatus = fileStatus;
|
||||
|
|
@ -50,6 +53,10 @@ public class FileInfo {
|
|||
return filePatternName;
|
||||
}
|
||||
|
||||
public String getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public String getDepartureDateTime() {
|
||||
return departureDateTime;
|
||||
}
|
||||
|
|
@ -73,13 +80,14 @@ public class FileInfo {
|
|||
fileName, fileInfo.fileName) && Objects.equals(filePatternCode,
|
||||
fileInfo.filePatternCode
|
||||
) && Objects.equals(filePatternName, fileInfo.filePatternName)
|
||||
&& Objects.equals(departureDateTime, fileInfo.departureDateTime);
|
||||
&& Objects.equals(departureDateTime, fileInfo.departureDateTime) &&
|
||||
Objects.equals(fileSize, fileInfo.getFileSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(fileId, fileUrl, fileName, filePatternCode, filePatternName,
|
||||
departureDateTime
|
||||
departureDateTime, fileSize
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +100,7 @@ public class FileInfo {
|
|||
", filePatternCode='" + filePatternCode + '\'' +
|
||||
", filePatternName='" + filePatternName + '\'' +
|
||||
", departureDateTime='" + departureDateTime + '\'' +
|
||||
", fileSize='" + fileSize + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
package ervu.model.fileupload;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author Eduard Tihomirov
|
||||
*/
|
||||
public class FileStatus {
|
||||
|
||||
private String code;
|
||||
private String status;
|
||||
private String description;
|
||||
|
|
|
|||
|
|
@ -7,18 +7,24 @@ import ru.micord.ervu.journal.SenderInfo;
|
|||
/**
|
||||
* @author Alexandr Shalaginov
|
||||
*/
|
||||
public class OrgInfo {
|
||||
public class UploadOrgInfo {
|
||||
private String orgName;
|
||||
private String orgId;
|
||||
private SenderInfo senderInfo;
|
||||
private String esiaOrgId;
|
||||
|
||||
public OrgInfo() {
|
||||
public UploadOrgInfo() {
|
||||
}
|
||||
|
||||
public OrgInfo(String orgName, String orgId, SenderInfo senderInfo) {
|
||||
public UploadOrgInfo(String orgName, String orgId, SenderInfo senderInfo, String esiaOrgId) {
|
||||
this.orgName = orgName;
|
||||
this.orgId = orgId;
|
||||
this.senderInfo = senderInfo;
|
||||
this.esiaOrgId = esiaOrgId;
|
||||
}
|
||||
|
||||
public String getEsiaOrgId() {
|
||||
return esiaOrgId;
|
||||
}
|
||||
|
||||
public String getOrgName() {
|
||||
|
|
@ -37,23 +43,25 @@ public class OrgInfo {
|
|||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
OrgInfo orgInfo = (OrgInfo) o;
|
||||
return Objects.equals(orgName, orgInfo.orgName) && Objects.equals(orgId,
|
||||
orgInfo.orgId
|
||||
) && Objects.equals(senderInfo, orgInfo.senderInfo);
|
||||
UploadOrgInfo uploadOrgInfo = (UploadOrgInfo) o;
|
||||
return Objects.equals(orgName, uploadOrgInfo.orgName) && Objects.equals(orgId,
|
||||
uploadOrgInfo.orgId
|
||||
) && Objects.equals(senderInfo, uploadOrgInfo.senderInfo)
|
||||
&& Objects.equals(esiaOrgId, uploadOrgInfo.esiaOrgId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(orgName, orgId, senderInfo);
|
||||
return Objects.hash(orgName, orgId, senderInfo, esiaOrgId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OrgInfo{" +
|
||||
return "UploadOrgInfo{" +
|
||||
"orgName='" + orgName + '\'' +
|
||||
", orgId='" + orgId + '\'' +
|
||||
", senderInfo='" + senderInfo + '\'' +
|
||||
", esiaOrgId='" + esiaOrgId + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import java.io.IOException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
|
@ -31,6 +30,7 @@ import org.springframework.kafka.annotation.KafkaListener;
|
|||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
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.model.EmployeeModel;
|
||||
import ru.micord.ervu.security.esia.model.PersonModel;
|
||||
|
|
@ -38,6 +38,7 @@ import ru.micord.ervu.security.esia.service.UlDataService;
|
|||
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
|
||||
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
|
||||
import ru.micord.ervu.service.InteractionService;
|
||||
import ru.micord.ervu.util.DateUtils;
|
||||
|
||||
import static ervu.enums.FileStatusCode.FILE_CLEAN;
|
||||
import static ervu.enums.FileStatusCode.FILE_INFECTED;
|
||||
|
|
@ -51,13 +52,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 FORMAT = "dd.MM.yyyy HH:mm:ss";
|
||||
|
||||
private final WebDavClient webDavClient;
|
||||
private final EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService;
|
||||
private final KafkaTemplate<String, String> kafkaTemplate;
|
||||
private final InteractionService interactionService;
|
||||
private final UlDataService ulDataService;
|
||||
private final AuditService auditService;
|
||||
|
||||
@Value("${av.kafka.message.topic.name}")
|
||||
private String kafkaTopicName;
|
||||
|
|
@ -67,12 +67,14 @@ public class EmployeeInfoFileUploadService {
|
|||
EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService,
|
||||
@Qualifier("avTemplate") KafkaTemplate<String, String> kafkaTemplate,
|
||||
InteractionService interactionService,
|
||||
UlDataService ulDataService) {
|
||||
UlDataService ulDataService,
|
||||
AuditService auditService) {
|
||||
this.webDavClient = webDavClient;
|
||||
this.employeeInfoKafkaMessageService = employeeInfoKafkaMessageService;
|
||||
this.kafkaTemplate = kafkaTemplate;
|
||||
this.interactionService = interactionService;
|
||||
this.ulDataService = ulDataService;
|
||||
this.auditService = auditService;
|
||||
}
|
||||
|
||||
public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType,
|
||||
|
|
@ -99,29 +101,34 @@ public class EmployeeInfoFileUploadService {
|
|||
convertToFio(personModel.getFirstName(), personModel.getMiddleName(), personModel.getLastName()),
|
||||
ervuId);
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
if (fileUploadUrl != null) {
|
||||
fileStatus.setCode(FILE_UPLOADED.getCode());
|
||||
fileStatus.setDescription("Файл принят до проверки на вирусы");
|
||||
String departureDateTime = now.format(DateTimeFormatter.ofPattern(FORMAT));
|
||||
String jsonMessage = getJsonKafkaMessage(
|
||||
employeeInfoKafkaMessageService.getKafkaMessage(
|
||||
fileId,
|
||||
fileUploadUrl,
|
||||
fileName,
|
||||
employeeInfoFileFormType,
|
||||
departureDateTime,
|
||||
accessToken,
|
||||
offset,
|
||||
fileStatus,
|
||||
ervuId,
|
||||
esiaUserId,
|
||||
personModel
|
||||
)
|
||||
);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -199,6 +206,7 @@ public class EmployeeInfoFileUploadService {
|
|||
interactionService.delete(fileInfo.getFileId(), downloadResponse.orgInfo().getOrgId());
|
||||
}
|
||||
else if (statusCode.equals(FILE_NOT_CHECKED.getCode())) {
|
||||
auditService.processUploadEvent(downloadResponse.orgInfo(), downloadResponse.fileInfo());
|
||||
interactionService.updateStatus(fileInfo.getFileId(), fileInfo.getFileStatus().getStatus(),
|
||||
downloadResponse.orgInfo().getOrgId()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import ervu.model.fileupload.EmployeeInfoFileFormType;
|
|||
import ervu.model.fileupload.EmployeeInfoKafkaMessage;
|
||||
import ervu.model.fileupload.FileInfo;
|
||||
import ervu.model.fileupload.FileStatus;
|
||||
import ervu.model.fileupload.OrgInfo;
|
||||
import ervu.model.fileupload.UploadOrgInfo;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.micord.ervu.journal.SenderInfo;
|
||||
import ru.micord.ervu.security.esia.model.OrganizationModel;
|
||||
|
|
@ -25,7 +25,8 @@ 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) {
|
||||
String offset, FileStatus fileStatus, String ervuId, String prnOid,
|
||||
PersonModel personModel, long fileSize) {
|
||||
return new EmployeeInfoKafkaMessage(
|
||||
getOrgInfo(accessToken, ervuId, prnOid, personModel),
|
||||
getFileInfo(
|
||||
|
|
@ -35,33 +36,36 @@ public class EmployeeInfoKafkaMessageService {
|
|||
formType,
|
||||
departureDateTime,
|
||||
offset,
|
||||
fileStatus
|
||||
fileStatus,
|
||||
fileSize
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private FileInfo getFileInfo(String fileId, String fileUrl, String fileName,
|
||||
EmployeeInfoFileFormType formType, String departureDateTime, String offset,
|
||||
FileStatus fileStatus) {
|
||||
FileStatus fileStatus, long fileSize) {
|
||||
return new FileInfo(
|
||||
fileId,
|
||||
fileUrl,
|
||||
fileName,
|
||||
formType.getFilePatternCode(),
|
||||
formType.getFilePatternName(),
|
||||
String.valueOf(fileSize),
|
||||
departureDateTime,
|
||||
offset,
|
||||
fileStatus
|
||||
);
|
||||
}
|
||||
|
||||
private OrgInfo getOrgInfo(String accessToken, String ervuId, String prnOid, PersonModel personModel) {
|
||||
private UploadOrgInfo getOrgInfo(String accessToken, String ervuId, String prnOid, PersonModel personModel) {
|
||||
OrganizationModel organizationModel = ulDataService.getOrganizationModel(accessToken);
|
||||
SenderInfo senderInfo = new SenderInfo();
|
||||
senderInfo.setFirstName(personModel.getFirstName());
|
||||
senderInfo.setLastName(personModel.getLastName());
|
||||
senderInfo.setMiddleName(personModel.getMiddleName());
|
||||
senderInfo.setPrnOid(prnOid);
|
||||
return new OrgInfo(organizationModel.getFullName(), ervuId, senderInfo);
|
||||
return new UploadOrgInfo(organizationModel.getFullName(), ervuId, senderInfo,
|
||||
organizationModel.getOid());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package ru.micord.ervu.audit.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.kafka.clients.CommonClientConfigs;
|
||||
import org.apache.kafka.clients.admin.AdminClientConfig;
|
||||
import org.apache.kafka.clients.admin.NewTopic;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.common.config.SaslConfigs;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.kafka.config.TopicBuilder;
|
||||
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
|
||||
import org.springframework.kafka.core.KafkaAdmin;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.kafka.core.ProducerFactory;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
@Configuration
|
||||
public class AuditKafkaConfig {
|
||||
@Value("${audit.kafka.bootstrap.servers}")
|
||||
private String bootstrapServers;
|
||||
@Value("${audit.kafka.security.protocol}")
|
||||
private String securityProtocol;
|
||||
@Value("${audit.kafka.login.module}")
|
||||
private String loginModule;
|
||||
@Value("${audit.kafka.username}")
|
||||
private String username;
|
||||
@Value("${audit.kafka.password}")
|
||||
private String password;
|
||||
@Value("${audit.kafka.sasl.mechanism}")
|
||||
private String saslMechanism;
|
||||
@Value("${audit.kafka.authorization.topic}")
|
||||
private String authorizationTopic;
|
||||
@Value("${audit.kafka.action.topic}")
|
||||
private String actionTopic;
|
||||
@Value("${audit.kafka.file.download.topic}")
|
||||
private String fileDownloadTopic;
|
||||
|
||||
|
||||
@Bean("auditProducerFactory")
|
||||
public ProducerFactory<String, String> producerFactory() {
|
||||
Map<String, Object> configProps = new HashMap<>();
|
||||
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
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 + "\";");
|
||||
configProps.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
|
||||
return new DefaultKafkaProducerFactory<>(configProps);
|
||||
}
|
||||
|
||||
@Bean("auditTemplate")
|
||||
public KafkaTemplate<String, String> kafkaTemplate() {
|
||||
return new KafkaTemplate<>(producerFactory());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KafkaAdmin auditKafkaAdmin() {
|
||||
Map<String, Object> configs = new HashMap<>();
|
||||
configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
configs.put(AdminClientConfig.SECURITY_PROTOCOL_CONFIG, securityProtocol);
|
||||
configs.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\""
|
||||
+ username + "\" password=\"" + password + "\";");
|
||||
configs.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
|
||||
return new KafkaAdmin(configs);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NewTopic auditAuthTopic() {
|
||||
return TopicBuilder.name(authorizationTopic).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NewTopic auditActionTopic() {
|
||||
return TopicBuilder.name(actionTopic).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NewTopic auditDownloadTopic() {
|
||||
return TopicBuilder.name(fileDownloadTopic).build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package ru.micord.ervu.audit.constants;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public final class AuditConstants {
|
||||
public static final String SUBSYSTEM_TYPE = "UL";
|
||||
public static final String LOGOUT_EVENT_TYPE = "logout";
|
||||
public static final String LOGIN_EVENT_TYPE = "login";
|
||||
public static final String SUCCESS_STATUS_TYPE = "success";
|
||||
public static final String FAILURE_STATUS_TYPE = "failure";
|
||||
|
||||
private static final Map<String, String> routeDescriptions = Map.of(
|
||||
"/", "Личный кабинет ЮР лица",
|
||||
"/mydata", "Информация об организации",
|
||||
"/filesentlog", "Журнал взаимодействия"
|
||||
);
|
||||
|
||||
private static final Map<Integer, String> downloadTypes = Map.of(
|
||||
1, "Выписка из журнала взаимодействия ЮЛ"
|
||||
);
|
||||
|
||||
|
||||
private AuditConstants() {
|
||||
}
|
||||
|
||||
public static String getRouteDescription(String route) {
|
||||
return Optional.ofNullable(routeDescriptions.get(route))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Invalid route :" + route));
|
||||
}
|
||||
|
||||
public static String getDownloadType(int formatRegistry) {
|
||||
return Optional.ofNullable(downloadTypes.get(formatRegistry))
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Invalid formatRegistry :" + formatRegistry));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package ru.micord.ervu.audit.controller;
|
||||
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import ru.micord.ervu.audit.model.AuditActionRequest;
|
||||
import ru.micord.ervu.audit.service.AuditService;
|
||||
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/audit")
|
||||
public class AuditController {
|
||||
private final AuditService auditService;
|
||||
|
||||
public AuditController(AuditService auditService) {
|
||||
this.auditService = auditService;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/action", method = RequestMethod.POST)
|
||||
public ResponseEntity<Void> auditAction(
|
||||
HttpServletRequest request, @RequestBody AuditActionRequest actionEvent) {
|
||||
auditService.processActionEvent(request, actionEvent);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package ru.micord.ervu.audit.model;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public class AuditActionEvent extends AuditEvent {
|
||||
private String eventType;
|
||||
private String description;
|
||||
private String sourceUrl;
|
||||
private List<SearchCriteria> searchAttributes;
|
||||
private String fileName;
|
||||
|
||||
public AuditActionEvent(
|
||||
String esiaOrgId, String esiaPersonId, String eventTime,
|
||||
String eventType, String description, String sourceUrl,
|
||||
List<SearchCriteria> searchAttributes, String fileName) {
|
||||
super(esiaOrgId, esiaPersonId, eventTime);
|
||||
this.eventType = eventType;
|
||||
this.description = description;
|
||||
this.sourceUrl = sourceUrl;
|
||||
this.searchAttributes = searchAttributes;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public void setEventType(String eventType) {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getSourceUrl() {
|
||||
return sourceUrl;
|
||||
}
|
||||
|
||||
public void setSourceUrl(String sourceUrl) {
|
||||
this.sourceUrl = sourceUrl;
|
||||
}
|
||||
|
||||
public List<SearchCriteria> getSearchAttributes() {
|
||||
return searchAttributes;
|
||||
}
|
||||
|
||||
public void setSearchAttributes(List<SearchCriteria> searchAttributes) {
|
||||
this.searchAttributes = searchAttributes;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package ru.micord.ervu.audit.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public class AuditActionRequest {
|
||||
private String eventType;
|
||||
private String route;
|
||||
private String sourceUrl;
|
||||
private Map<String, FilterInfo> filterInfo;
|
||||
private String fileName;
|
||||
|
||||
public String getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public void setEventType(String eventType) {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
public String getRoute() {
|
||||
return route;
|
||||
}
|
||||
|
||||
public void setRoute(String route) {
|
||||
this.route = route;
|
||||
}
|
||||
|
||||
public String getSourceUrl() {
|
||||
return sourceUrl;
|
||||
}
|
||||
|
||||
public void setSourceUrl(String sourceUrl) {
|
||||
this.sourceUrl = sourceUrl;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public Map<String, FilterInfo> getFilterInfo() {
|
||||
return filterInfo;
|
||||
}
|
||||
|
||||
public void setFilterInfo(
|
||||
Map<String, FilterInfo> filterInfo) {
|
||||
this.filterInfo = filterInfo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package ru.micord.ervu.audit.model;
|
||||
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
|
||||
public class AuditAuthorizationEvent extends AuditEvent {
|
||||
private String status;
|
||||
private String eventType;
|
||||
private String organizationName;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String middleName;
|
||||
private String inn;
|
||||
private String serverIp;
|
||||
private String serverHostName;
|
||||
private String clientIp;
|
||||
private String clientHostName;
|
||||
|
||||
public AuditAuthorizationEvent(
|
||||
String esiaOrgId, String esiaPersonId, String eventTime,
|
||||
String organizationName, String firstName, String lastName,
|
||||
String middleName, String inn, String status,
|
||||
String eventType, String serverIp, String serverHostName,
|
||||
String clientIp, String clientHostName) {
|
||||
super(esiaOrgId, esiaPersonId, eventTime);
|
||||
this.status = status;
|
||||
this.eventType = eventType;
|
||||
this.organizationName = organizationName;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.middleName = middleName;
|
||||
this.inn = inn;
|
||||
this.serverIp = serverIp;
|
||||
this.serverHostName = serverHostName;
|
||||
this.clientIp = clientIp;
|
||||
this.clientHostName = clientHostName;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public void setEventType(String eventType) {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
public String getOrganizationName() {
|
||||
return organizationName;
|
||||
}
|
||||
|
||||
public void setOrganizationName(String organizationName) {
|
||||
this.organizationName = organizationName;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getMiddleName() {
|
||||
return middleName;
|
||||
}
|
||||
|
||||
public void setMiddleName(String middleName) {
|
||||
this.middleName = middleName;
|
||||
}
|
||||
|
||||
public String getInn() {
|
||||
return inn;
|
||||
}
|
||||
|
||||
public void setInn(String inn) {
|
||||
this.inn = inn;
|
||||
}
|
||||
|
||||
public String getServerIp() {
|
||||
return serverIp;
|
||||
}
|
||||
|
||||
public void setServerIp(String serverIp) {
|
||||
this.serverIp = serverIp;
|
||||
}
|
||||
|
||||
public String getServerHostName() {
|
||||
return serverHostName;
|
||||
}
|
||||
|
||||
public void setServerHostName(String serverHostName) {
|
||||
this.serverHostName = serverHostName;
|
||||
}
|
||||
|
||||
public String getClientIp() {
|
||||
return clientIp;
|
||||
}
|
||||
|
||||
public void setClientIp(String clientIp) {
|
||||
this.clientIp = clientIp;
|
||||
}
|
||||
|
||||
public String getClientHostName() {
|
||||
return clientHostName;
|
||||
}
|
||||
|
||||
public void setClientHostName(String clientHostName) {
|
||||
this.clientHostName = clientHostName;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package ru.micord.ervu.audit.model;
|
||||
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
|
||||
public class AuditDownloadEvent extends AuditEvent {
|
||||
private String downloadType;
|
||||
private String fileName;
|
||||
private String s3FileUrl;
|
||||
private String fileSize;
|
||||
private String status;
|
||||
|
||||
public AuditDownloadEvent(
|
||||
String esiaOrgId, String esiaPersonId, String eventTime,
|
||||
String downloadType, String fileName, String s3FileUrl,
|
||||
String fileSize, String status) {
|
||||
super(esiaOrgId, esiaPersonId, eventTime);
|
||||
this.downloadType = downloadType;
|
||||
this.fileName = fileName;
|
||||
this.s3FileUrl = s3FileUrl;
|
||||
this.fileSize = fileSize;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getDownloadType() {
|
||||
return downloadType;
|
||||
}
|
||||
|
||||
public void setDownloadType(String downloadType) {
|
||||
this.downloadType = downloadType;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getS3FileUrl() {
|
||||
return s3FileUrl;
|
||||
}
|
||||
|
||||
public void setS3FileUrl(String s3FileUrl) {
|
||||
this.s3FileUrl = s3FileUrl;
|
||||
}
|
||||
|
||||
public String getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public void setFileSize(String fileSize) {
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package ru.micord.ervu.audit.model;
|
||||
|
||||
import ru.micord.ervu.audit.constants.AuditConstants;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public abstract class AuditEvent {
|
||||
protected final String subsystem = AuditConstants.SUBSYSTEM_TYPE;
|
||||
protected String esiaOrgId;
|
||||
protected String esiaPersonId;
|
||||
protected String eventTime;
|
||||
|
||||
public AuditEvent(
|
||||
String esiaOrgId, String esiaPersonId, String eventTime) {
|
||||
this.esiaOrgId = esiaOrgId;
|
||||
this.esiaPersonId = esiaPersonId;
|
||||
this.eventTime = eventTime;
|
||||
}
|
||||
|
||||
public String getSubsystem() {
|
||||
return subsystem;
|
||||
}
|
||||
|
||||
public String getEsiaOrgId() {
|
||||
return esiaOrgId;
|
||||
}
|
||||
|
||||
public void setEsiaOrgId(String esiaOrgId) {
|
||||
this.esiaOrgId = esiaOrgId;
|
||||
}
|
||||
|
||||
public String getEsiaPersonId() {
|
||||
return esiaPersonId;
|
||||
}
|
||||
|
||||
public void setEsiaPersonId(String esiaPersonId) {
|
||||
this.esiaPersonId = esiaPersonId;
|
||||
}
|
||||
|
||||
public String getEventTime() {
|
||||
return eventTime;
|
||||
}
|
||||
|
||||
public void setEventTime(String eventTime) {
|
||||
this.eventTime = eventTime;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package ru.micord.ervu.audit.model;
|
||||
|
||||
|
||||
import ervu.model.fileupload.FileInfo;
|
||||
import ervu.model.fileupload.UploadOrgInfo;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
|
||||
public class AuditUploadEvent {
|
||||
private UploadOrgInfo orgInfo;
|
||||
private FileInfo fileInfo;
|
||||
|
||||
public AuditUploadEvent(UploadOrgInfo orgInfo, FileInfo fileInfo) {
|
||||
this.orgInfo = orgInfo;
|
||||
this.fileInfo = fileInfo;
|
||||
}
|
||||
|
||||
public UploadOrgInfo getOrgInfo() {
|
||||
return orgInfo;
|
||||
}
|
||||
|
||||
public void setOrgInfo(UploadOrgInfo orgInfo) {
|
||||
this.orgInfo = orgInfo;
|
||||
}
|
||||
|
||||
public FileInfo getFileInfo() {
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
public void setFileInfo(FileInfo fileInfo) {
|
||||
this.fileInfo = fileInfo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package ru.micord.ervu.audit.model;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public class FilterCondition {
|
||||
private String filterValue;
|
||||
private String filterType;
|
||||
|
||||
public String getFilterValue() {
|
||||
return filterValue;
|
||||
}
|
||||
|
||||
public void setFilterValue(String filterValue) {
|
||||
this.filterValue = filterValue;
|
||||
}
|
||||
|
||||
public String getFilterType() {
|
||||
return filterType;
|
||||
}
|
||||
|
||||
public void setFilterType(String filterType) {
|
||||
this.filterType = filterType;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package ru.micord.ervu.audit.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
|
||||
public class FilterInfo {
|
||||
private String conditionOperator;
|
||||
private List<FilterCondition> conditions;
|
||||
|
||||
public String getConditionOperator() {
|
||||
return conditionOperator;
|
||||
}
|
||||
|
||||
public void setConditionOperator(String conditionOperator) {
|
||||
this.conditionOperator = conditionOperator;
|
||||
}
|
||||
|
||||
public List<FilterCondition> getConditions() {
|
||||
return conditions;
|
||||
}
|
||||
|
||||
public void setConditions(List<FilterCondition> conditions) {
|
||||
this.conditions = conditions;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package ru.micord.ervu.audit.model;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public class SearchCriteria {
|
||||
private String searchAttribute;
|
||||
private String searchValue;
|
||||
|
||||
public SearchCriteria(String searchAttribute, String searchValue) {
|
||||
this.searchAttribute = searchAttribute;
|
||||
this.searchValue = searchValue;
|
||||
}
|
||||
|
||||
public String getSearchAttribute() {
|
||||
return searchAttribute;
|
||||
}
|
||||
|
||||
public void setSearchAttribute(String searchAttribute) {
|
||||
this.searchAttribute = searchAttribute;
|
||||
}
|
||||
|
||||
public String getSearchValue() {
|
||||
return searchValue;
|
||||
}
|
||||
|
||||
public void setSearchValue(String searchValue) {
|
||||
this.searchValue = searchValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.micord.ervu.audit.service;
|
||||
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public interface AuditKafkaPublisher {
|
||||
void publishEvent(String topicName, String message);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package ru.micord.ervu.audit.service;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import ervu.model.fileupload.FileInfo;
|
||||
import ervu.model.fileupload.UploadOrgInfo;
|
||||
import ru.micord.ervu.audit.model.AuditActionRequest;
|
||||
import ru.micord.ervu.kafka.model.OrgInfo;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public interface AuditService {
|
||||
void processActionEvent(HttpServletRequest request, AuditActionRequest auditActionRequest);
|
||||
|
||||
void processAuthEvent(HttpServletRequest request, OrgInfo orgInfo, String prnOid, String status,
|
||||
String eventType);
|
||||
|
||||
void processUploadEvent(UploadOrgInfo uploadOrgInfo, FileInfo fileInfo);
|
||||
|
||||
void processDownloadEvent(HttpServletRequest request, long fileSize, String fileName,
|
||||
int formatRegistry, String status, String s3FileUrl);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package ru.micord.ervu.audit.service.impl;
|
||||
|
||||
|
||||
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.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.micord.ervu.audit.service.AuditKafkaPublisher;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
@Service
|
||||
public class BaseAuditKafkaPublisher implements AuditKafkaPublisher {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BaseAuditKafkaPublisher.class);
|
||||
private final KafkaTemplate<String, String> kafkaTemplate;
|
||||
@Value("${audit.kafka.enabled}")
|
||||
private boolean auditEnabled;
|
||||
|
||||
public BaseAuditKafkaPublisher(
|
||||
@Qualifier("auditTemplate") KafkaTemplate<String, String> kafkaTemplate) {
|
||||
this.kafkaTemplate = kafkaTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishEvent(String topic, String message) {
|
||||
if (auditEnabled) {
|
||||
kafkaTemplate.send(topic, message)
|
||||
.addCallback(
|
||||
result -> {
|
||||
},
|
||||
ex -> LOGGER.error("Failed to send message to topic {}: {}", topic, ex.getMessage(),
|
||||
ex
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
LOGGER.info("Audit is disabled. Event not published.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
package ru.micord.ervu.audit.service.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ervu.model.fileupload.FileInfo;
|
||||
import ervu.model.fileupload.UploadOrgInfo;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.micord.ervu.audit.constants.AuditConstants;
|
||||
import ru.micord.ervu.audit.model.*;
|
||||
import ru.micord.ervu.audit.service.AuditKafkaPublisher;
|
||||
import ru.micord.ervu.audit.service.AuditService;
|
||||
import ru.micord.ervu.exception.JsonParsingException;
|
||||
import ru.micord.ervu.kafka.model.OrgInfo;
|
||||
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
|
||||
import ru.micord.ervu.security.esia.service.UlDataService;
|
||||
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
|
||||
import ru.micord.ervu.util.DateUtils;
|
||||
import ru.micord.ervu.util.NetworkUtils;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
@Service
|
||||
public class BaseAuditService implements AuditService {
|
||||
private final AuditKafkaPublisher auditKafkaPublisher;
|
||||
private final JwtTokenService jwtTokenService;
|
||||
private final UlDataService ulDataService;
|
||||
private final ObjectMapper objectMapper;
|
||||
@Value("${audit.kafka.authorization.topic}")
|
||||
private String authorizationTopic;
|
||||
@Value("${audit.kafka.action.topic}")
|
||||
private String actionTopic;
|
||||
@Value("${audit.kafka.file.upload.topic}")
|
||||
private String fileUploadTopic;
|
||||
@Value("${audit.kafka.file.download.topic}")
|
||||
private String fileDownloadTopic;
|
||||
|
||||
public BaseAuditService(AuditKafkaPublisher auditKafkaPublisher, JwtTokenService jwtTokenService,
|
||||
UlDataService ulDataService, ObjectMapper objectMapper) {
|
||||
this.auditKafkaPublisher = auditKafkaPublisher;
|
||||
this.jwtTokenService = jwtTokenService;
|
||||
this.ulDataService = ulDataService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processActionEvent(HttpServletRequest request,
|
||||
AuditActionRequest auditActionRequest) {
|
||||
String orgId = getEsiaOrgId(request);
|
||||
String userAccountId = jwtTokenService.getUserAccountId(request);
|
||||
String description = AuditConstants.getRouteDescription(auditActionRequest.getRoute());
|
||||
|
||||
List<SearchCriteria> searchAttributes = null;
|
||||
if (auditActionRequest.getFilterInfo() != null && !auditActionRequest.getFilterInfo().isEmpty()) {
|
||||
searchAttributes = getSearchCriteriaList(auditActionRequest.getFilterInfo());
|
||||
}
|
||||
|
||||
AuditActionEvent event = new AuditActionEvent(
|
||||
orgId,
|
||||
userAccountId,
|
||||
DateUtils.getClientDateTimeWithZoneFromRequest(request),
|
||||
auditActionRequest.getEventType(),
|
||||
description,
|
||||
auditActionRequest.getSourceUrl(),
|
||||
searchAttributes,
|
||||
auditActionRequest.getFileName()
|
||||
);
|
||||
|
||||
String message = convertToMessage(event);
|
||||
auditKafkaPublisher.publishEvent(actionTopic, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAuthEvent(HttpServletRequest request, OrgInfo orgInfo, String prnOid,
|
||||
String status, String eventType) {
|
||||
String serverIp = NetworkUtils.getServerIp();
|
||||
String clientIp = NetworkUtils.getClientIp(request);
|
||||
String serverHostName = NetworkUtils.getHostName(serverIp);
|
||||
String clientHostName = NetworkUtils.getHostName(clientIp);
|
||||
|
||||
AuditAuthorizationEvent event = new AuditAuthorizationEvent(
|
||||
orgInfo.getOrgOid(),
|
||||
prnOid,
|
||||
DateUtils.getClientDateTimeWithZoneFromRequest(request),
|
||||
orgInfo.getOrgFullName(),
|
||||
orgInfo.getSenderInfo().getFirstName(),
|
||||
orgInfo.getSenderInfo().getLastName(),
|
||||
orgInfo.getSenderInfo().getMiddleName(),
|
||||
orgInfo.getInn(),
|
||||
status,
|
||||
eventType,
|
||||
serverIp,
|
||||
serverHostName,
|
||||
clientIp,
|
||||
clientHostName
|
||||
);
|
||||
|
||||
String message = convertToMessage(event);
|
||||
auditKafkaPublisher.publishEvent(authorizationTopic, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUploadEvent(UploadOrgInfo orgInfo, FileInfo fileInfo) {
|
||||
AuditUploadEvent auditUploadEvent = new AuditUploadEvent(
|
||||
orgInfo,
|
||||
fileInfo
|
||||
);
|
||||
|
||||
String message = convertToMessage(auditUploadEvent);
|
||||
auditKafkaPublisher.publishEvent(fileUploadTopic, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processDownloadEvent(
|
||||
HttpServletRequest request, long fileSize, String fileName, int formatRegistry,
|
||||
String status, String s3FileUrl) {
|
||||
String userAccountId = jwtTokenService.getUserAccountId(request);
|
||||
|
||||
AuditDownloadEvent event = new AuditDownloadEvent(
|
||||
getEsiaOrgId(request),
|
||||
userAccountId,
|
||||
DateUtils.getClientDateTimeWithZoneFromRequest(request),
|
||||
AuditConstants.getDownloadType(formatRegistry),
|
||||
fileName,
|
||||
s3FileUrl,
|
||||
String.valueOf(fileSize),
|
||||
status
|
||||
);
|
||||
|
||||
String message = convertToMessage(event);
|
||||
auditKafkaPublisher.publishEvent(fileDownloadTopic, message);
|
||||
}
|
||||
|
||||
private String getEsiaOrgId(HttpServletRequest request) {
|
||||
String accessToken = jwtTokenService.getAccessToken(request);
|
||||
EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken);
|
||||
String scope = esiaAccessToken.getScope();
|
||||
return scope.substring(scope.indexOf('=') + 1, scope.indexOf(' '));
|
||||
}
|
||||
|
||||
public List<SearchCriteria> getSearchCriteriaList(Map<String, FilterInfo> filterInfoMap) {
|
||||
List<SearchCriteria> searchCriteriaList = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, FilterInfo> entry : filterInfoMap.entrySet()) {
|
||||
String searchAttribute = entry.getKey();
|
||||
FilterInfo filterInfo = entry.getValue();
|
||||
String searchValue = filterInfo.getConditions().stream()
|
||||
.map(condition -> condition.getFilterValue() + " " + condition.getFilterType())
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
if (filterInfo.getConditionOperator() != null) {
|
||||
searchValue += "| Operator: " + filterInfo.getConditionOperator();
|
||||
}
|
||||
|
||||
SearchCriteria searchCriteria = new SearchCriteria(searchAttribute, searchValue);
|
||||
searchCriteriaList.add(searchCriteria);
|
||||
}
|
||||
|
||||
return searchCriteriaList;
|
||||
}
|
||||
|
||||
private String convertToMessage(Object event) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(event);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new JsonParsingException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class SenderInfo {
|
||||
|
||||
@JsonProperty("prnOid")
|
||||
private String prnOid; // идентификатор сотрудника в ЕСИА
|
||||
@JsonProperty("lastName")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import java.time.LocalDateTime;
|
|||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import ru.micord.ervu.util.DateUtil;
|
||||
import ru.micord.ervu.util.DateUtils;
|
||||
|
||||
public class DepartureDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
|
||||
|
||||
|
|
@ -14,6 +14,6 @@ public class DepartureDateTimeDeserializer extends JsonDeserializer<LocalDateTim
|
|||
public LocalDateTime deserialize(JsonParser jsonParser,
|
||||
DeserializationContext deserializationContext) throws IOException {
|
||||
String dateTimeString = jsonParser.getText();
|
||||
return DateUtil.convertToLocalDateTime(dateTimeString);
|
||||
return DateUtils.convertToLocalDateTime(dateTimeString);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,26 @@ package ru.micord.ervu.kafka.controller;
|
|||
import java.time.ZonedDateTime;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ervu.client.fileupload.WebDavClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import ru.micord.ervu.audit.constants.AuditConstants;
|
||||
import ru.micord.ervu.audit.service.AuditService;
|
||||
import ru.micord.ervu.kafka.exception.ExcerptException;
|
||||
import ru.micord.ervu.kafka.exception.ExcerptResponseException;
|
||||
import ru.micord.ervu.kafka.model.Data;
|
||||
import ru.micord.ervu.kafka.model.ExcerptResponse;
|
||||
import ru.micord.ervu.kafka.service.ReplyingKafkaService;
|
||||
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
|
||||
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
|
||||
import ru.micord.ervu.util.UrlUtils;
|
||||
|
||||
/**
|
||||
* @author Eduard Tihomirov
|
||||
|
|
@ -27,6 +33,9 @@ public class ErvuKafkaController {
|
|||
@Autowired
|
||||
private ReplyingKafkaService replyingKafkaService;
|
||||
|
||||
@Autowired
|
||||
private AuditService auditService;
|
||||
|
||||
@Autowired
|
||||
private WebDavClient webDavClient;
|
||||
|
||||
|
|
@ -40,10 +49,12 @@ public class ErvuKafkaController {
|
|||
private ObjectMapper objectMapper;
|
||||
|
||||
@RequestMapping(value = "/kafka/excerpt")
|
||||
public ResponseEntity<Resource> getExcerptFile(
|
||||
@RequestHeader("Client-Time-Zone") String clientTimeZone) {
|
||||
|
||||
public ResponseEntity<Resource> getExcerptFile(HttpServletRequest request) {
|
||||
String fileUrl = null;
|
||||
String fileName = null;
|
||||
long fileSize = 0;
|
||||
try {
|
||||
String clientTimeZone = request.getHeader("Client-Time-Zone");
|
||||
UserIdsPair userIdsPair = SecurityUtil.getUserIdsPair();
|
||||
Data data = new Data();
|
||||
data.setErvuId(userIdsPair.getErvuId());
|
||||
|
|
@ -56,16 +67,30 @@ public class ErvuKafkaController {
|
|||
ExcerptResponse excerptResponse = objectMapper.readValue(kafkaResponse, ExcerptResponse.class);
|
||||
|
||||
if (!excerptResponse.getSuccess()) {
|
||||
throw new RuntimeException("Error with getting excerpt url " + excerptResponse.getMessage());
|
||||
throw new ExcerptResponseException(
|
||||
"Error with getting excerpt url " + excerptResponse.getMessage());
|
||||
}
|
||||
else if (excerptResponse.getData() == null || excerptResponse.getData().getFileUrl() == null
|
||||
|| excerptResponse.getData().getFileUrl().isEmpty()) {
|
||||
auditService.processDownloadEvent(request, fileSize, fileName, 1,
|
||||
AuditConstants.FAILURE_STATUS_TYPE, fileUrl
|
||||
);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
return webDavClient.webDavDownloadFile(excerptResponse.getData().getFileUrl());
|
||||
fileUrl = excerptResponse.getData().getFileUrl();
|
||||
fileName = UrlUtils.extractFileNameFromUrl(excerptResponse.getData().getFileUrl());
|
||||
ResponseEntity<Resource> responseEntity = webDavClient.webDavDownloadFile(fileUrl);
|
||||
fileSize = responseEntity.getHeaders().getContentLength();
|
||||
auditService.processDownloadEvent(request, fileSize, fileName, 1,
|
||||
AuditConstants.SUCCESS_STATUS_TYPE, fileUrl
|
||||
);
|
||||
return responseEntity;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
auditService.processDownloadEvent(request, fileSize, fileName, 1,
|
||||
AuditConstants.FAILURE_STATUS_TYPE, fileUrl
|
||||
);
|
||||
throw new ExcerptException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package ru.micord.ervu.kafka.exception;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public class ExcerptException extends RuntimeException {
|
||||
public ExcerptException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ExcerptException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ExcerptException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.micord.ervu.kafka.exception;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public class ExcerptResponseException extends RuntimeException {
|
||||
public ExcerptResponseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT;
|
|||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
private static final String[] PERMIT_ALL = new String[] {
|
||||
"/version", "/esia/url", "/esia/auth", "esia/refresh", "/esia/logout",
|
||||
"/version", "/esia/url", "/esia/auth", "esia/refresh", "/esia/logout"
|
||||
};
|
||||
@Autowired
|
||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
import ervu.service.okopf.OkopfService;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.micord.ervu.audit.constants.AuditConstants;
|
||||
import ru.micord.ervu.audit.service.AuditService;
|
||||
import ru.micord.ervu.security.esia.exception.EsiaException;
|
||||
import ru.micord.ervu.security.esia.model.EmployeeModel;
|
||||
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
|
||||
|
|
@ -80,6 +82,8 @@ public class EsiaAuthService {
|
|||
private OkopfService okopfService;
|
||||
@Autowired
|
||||
private SecurityHelper securityHelper;
|
||||
@Autowired
|
||||
private AuditService auditService;
|
||||
@Value("${ervu.kafka.org.reply.topic}")
|
||||
private String requestReplyTopic;
|
||||
|
||||
|
|
@ -244,7 +248,7 @@ public class EsiaAuthService {
|
|||
Thread.currentThread().getId(), timeSignSecret, timeRequestAccessToken, timeVerifySecret);
|
||||
}
|
||||
OrgInfo orgInfo = null;
|
||||
String ervuId = null;
|
||||
String status = null, ervuId = null;
|
||||
try {
|
||||
orgInfo = getOrgInfo(esiaAccessTokenStr);
|
||||
hasRole = ulDataService.checkRole(esiaAccessTokenStr);
|
||||
|
|
@ -253,11 +257,19 @@ public class EsiaAuthService {
|
|||
LOGGER.error("The user with id = " + prnOid + " does not have the required role");
|
||||
throw new LocalizedException("access_denied", MESSAGE_SOURCE);
|
||||
}
|
||||
status = AuditConstants.SUCCESS_STATUS_TYPE;
|
||||
}
|
||||
catch (EsiaException | JsonProcessingException e) {
|
||||
throw new EsiaException(e);
|
||||
catch (Exception e) {
|
||||
status = AuditConstants.FAILURE_STATUS_TYPE;
|
||||
if (e instanceof EsiaException || e instanceof JsonProcessingException) {
|
||||
throw new EsiaException(e);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (orgInfo!= null){
|
||||
auditService.processAuthEvent(request, orgInfo, prnOid, status,
|
||||
AuditConstants.LOGIN_EVENT_TYPE);
|
||||
}
|
||||
createTokenAndAddCookie(response, prnOid, ervuId, hasRole , expiresIn);
|
||||
}
|
||||
}
|
||||
|
|
@ -369,9 +381,13 @@ public class EsiaAuthService {
|
|||
}
|
||||
|
||||
public String logout(HttpServletRequest request, HttpServletResponse response) {
|
||||
OrgInfo orgInfo = null;
|
||||
String userId = null;
|
||||
try {
|
||||
userId = jwtTokenService.getUserAccountId(request);
|
||||
String accessToken = EsiaTokensStore.getAccessToken(userId);
|
||||
orgInfo = getOrgInfo(accessToken);
|
||||
securityHelper.clearAccessCookies(response);
|
||||
String userId = jwtTokenService.getUserAccountId(request);
|
||||
EsiaTokensStore.removeAccessToken(userId);
|
||||
EsiaTokensStore.removeRefreshToken(userId);
|
||||
String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl();
|
||||
|
|
@ -380,9 +396,17 @@ public class EsiaAuthService {
|
|||
Map<String, String> params = mapOf(
|
||||
"client_id", esiaConfig.getClientId(),
|
||||
"redirect_url", redirectUrl);
|
||||
auditService.processAuthEvent(request, orgInfo, userId, AuditConstants.SUCCESS_STATUS_TYPE,
|
||||
AuditConstants.LOGOUT_EVENT_TYPE
|
||||
);
|
||||
return buildUrl(url, params);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (orgInfo != null) {
|
||||
auditService.processAuthEvent(request, orgInfo, userId, AuditConstants.FAILURE_STATUS_TYPE,
|
||||
AuditConstants.LOGOUT_EVENT_TYPE
|
||||
);
|
||||
}
|
||||
throw new EsiaException(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package ru.micord.ervu.util;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.util.StringUtils.hasText;
|
||||
|
|
@ -11,12 +12,31 @@ import static org.springframework.util.StringUtils.hasText;
|
|||
/**
|
||||
* @author gulnaz
|
||||
*/
|
||||
public final class DateUtil {
|
||||
|
||||
public final class DateUtils {
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy");
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");
|
||||
private static final DateTimeFormatter DATE_TIME_WITH_TIMEZONE_FORMATTER = DateTimeFormatter.ofPattern(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSX");
|
||||
|
||||
private DateUtil() {}
|
||||
private DateUtils() {
|
||||
}
|
||||
|
||||
public static String getCurrentFormattedDateTimeWithZone(){
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
return now.format(DATE_TIME_WITH_TIMEZONE_FORMATTER);
|
||||
}
|
||||
|
||||
public static String getClientDateTimeWithZoneFromRequest(HttpServletRequest request) {
|
||||
String clientTimeZone = request.getHeader("Client-Time-Zone");
|
||||
ZoneId zoneId;
|
||||
try {
|
||||
zoneId = ZoneId.of(clientTimeZone);
|
||||
}
|
||||
catch (Exception e) {
|
||||
zoneId = ZoneId.systemDefault();
|
||||
}
|
||||
return ZonedDateTime.now(zoneId).format(DATE_TIME_WITH_TIMEZONE_FORMATTER);
|
||||
}
|
||||
|
||||
public static LocalDate convertToLocalDate(String date) {
|
||||
return StringUtils.hasText(date)
|
||||
|
|
@ -30,6 +50,10 @@ public final class DateUtil {
|
|||
: null;
|
||||
}
|
||||
|
||||
public static String convertToString(LocalDateTime dateTime) {
|
||||
return dateTime == null ? "" : dateTime.format(DATE_TIME_FORMATTER);
|
||||
}
|
||||
|
||||
public static String convertToString(LocalDate date) {
|
||||
return date == null ? "" : date.format(DATE_FORMATTER);
|
||||
}
|
||||
53
backend/src/main/java/ru/micord/ervu/util/NetworkUtils.java
Normal file
53
backend/src/main/java/ru/micord/ervu/util/NetworkUtils.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package ru.micord.ervu.util;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public final class NetworkUtils {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NetworkUtils.class);
|
||||
private static final String IP_HEADER = "X-Forwarded-For";
|
||||
private static final String UNKNOWN = "unknown";
|
||||
|
||||
private NetworkUtils() {
|
||||
}
|
||||
|
||||
public static String getServerIp() {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getLocalHost();
|
||||
return inetAddress.getHostAddress();
|
||||
}
|
||||
catch (UnknownHostException e) {
|
||||
LOGGER.error("Failed to get local IP address", e);
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getClientIp(HttpServletRequest request) {
|
||||
String ip = request.getHeader(IP_HEADER);
|
||||
if (StringUtils.hasText(ip) && !ip.equalsIgnoreCase(UNKNOWN)) {
|
||||
return ip.split(",")[0].trim();
|
||||
}
|
||||
else {
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getHostName(String ip) {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getByName(ip);
|
||||
return inetAddress.getHostName();
|
||||
}
|
||||
catch (UnknownHostException e) {
|
||||
LOGGER.error("Unknown host for IP {}", ip, e);
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
package ru.micord.ervu.util;
|
||||
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||
import static org.apache.commons.lang3.StringUtils.substring;
|
||||
|
||||
public class StringUtils {
|
||||
public final class StringUtils {
|
||||
|
||||
private StringUtils() {
|
||||
}
|
||||
|
||||
public static String convertToFio(String firstName, String middleName, String lastName) {
|
||||
String firstNameInitial = substring(firstName, 0, 1).toUpperCase();
|
||||
|
|
|
|||
18
backend/src/main/java/ru/micord/ervu/util/UrlUtils.java
Normal file
18
backend/src/main/java/ru/micord/ervu/util/UrlUtils.java
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package ru.micord.ervu.util;
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author Adel Kalimullin
|
||||
*/
|
||||
public final class UrlUtils {
|
||||
|
||||
private UrlUtils(){
|
||||
}
|
||||
|
||||
public static String extractFileNameFromUrl(String url) {
|
||||
String path = URI.create(url).getPath();
|
||||
return path.substring(path.lastIndexOf('/') + 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
error.unknown=The system is temporarily unavailable. Please try again later.
|
||||
error.unknown=The system is temporarily unavailable. Please try again later.
|
||||
14
config.md
14
config.md
|
|
@ -828,3 +828,17 @@ JBPM использует 3 корневых категории логирова
|
|||
- `ERVU_KAFKA_EXCERPT_REQUEST_TOPIC` - топик для записи запроса для получения выписки по журналу взаимодействия
|
||||
- `ERVU_KAFKA_EXCERPT_REPLY_TOPIC` - топик для чтения выписки по журналу взаимодействия. Содержит ссылку на S3 с файлом выписки
|
||||
- `DB.JOURNAL.EXCLUDED.STATUSES` - статусы файла, которые необходимо исключить при получении данных по журналу взаимодействия из базы данных приложения
|
||||
|
||||
|
||||
#### Взаимодействие с Kafka audit
|
||||
- `AUDIT_KAFKA_AUTHORIZATION_TOPIC` - топик для отправки аудита в журнал авторизации
|
||||
- `AUDIT_KAFKA_ACTION_TOPIC` - топик для отправки аудита в журнал действий пользователя
|
||||
- `AUDIT_KAFKA_FILE_UPLOAD_TOPIC` - топик для отправки аудита в журнал обмена файлами
|
||||
- `AUDIT_KAFKA_FILE_DOWNLOAD_TOPIC` - топик для отправки аудита в журнал загрузки ЮЛ и ФЛ
|
||||
- `AUDIT_KAFKA_BOOTSTRAP_SERVERS` - список пар хост:порт, использующихся для установки первоначального соединения с кластером Kafka
|
||||
- `AUDIT_KAFKA_SECURITY_PROTOCOL` - протокол, используемый для взаимодействия с брокерами
|
||||
- `AUDIT_KAFKA_DOC_LOGIN_MODULE` - имя класса для входа в систему для SASL-соединений в формате, используемом конфигурационными файлами JAAS
|
||||
- `AUDIT_KAFKA_USERNAME` - пользователь для подключения к Kafka
|
||||
- `AUDIT_KAFKA_PASSWORD` - пароль для подключения к Kafka
|
||||
- `AUDIT_KAFKA_SASL_MECHANISM` - механизм SASL, используемый для клиентских подключений
|
||||
- `AUDIT_KAFKA_ENABLED` - флажок для включения записи аудита в кафку
|
||||
|
|
|
|||
|
|
@ -46,6 +46,18 @@ ESNSI_OKOPF_RETRY_DELAY_LOAD=3000
|
|||
ESNSI_OKOPF_CONNECT_TIMEOUT=2
|
||||
ESNSI_OKOPF_READ_TIMEOUT=4
|
||||
|
||||
AUDIT_KAFKA_AUTHORIZATION_TOPIC=ervu.lkrp.auth.events
|
||||
AUDIT_KAFKA_ACTION_TOPIC=ervu.lkrp.action.events
|
||||
AUDIT_KAFKA_FILE_UPLOAD_TOPIC=ervu.lkrp.download.request
|
||||
AUDIT_KAFKA_FILE_DOWNLOAD_TOPIC=ervu.lkrp.import.file
|
||||
AUDIT_KAFKA_BOOTSTRAP_SERVERS=
|
||||
AUDIT_KAFKA_SECURITY_PROTOCOL=
|
||||
AUDIT_KAFKA_DOC_LOGIN_MODULE=
|
||||
AUDIT_KAFKA_USERNAME=
|
||||
AUDIT_KAFKA_PASSWORD=
|
||||
AUDIT_KAFKA_SASL_MECHANISM=
|
||||
AUDIT_KAFKA_ENABLED=false
|
||||
|
||||
ERVU_FILE_UPLOAD_MAX_FILE_SIZE=5242880
|
||||
ERVU_FILE_UPLOAD_MAX_REQUEST_SIZE=6291456
|
||||
ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD=0
|
||||
|
|
|
|||
|
|
@ -100,6 +100,17 @@
|
|||
<property name="file.webdav.lifetime.seconds" value="300"/>
|
||||
<property name="file.webdav.extensions" value="csv,xlsx"/>
|
||||
<property name="webdav.bad_servers.cache.expire.seconds" value="120"/>
|
||||
<property name="audit.kafka.bootstrap.servers" value="localhost:9092"/>
|
||||
<property name="audit.kafka.authorization.topic" value="ervu.lkrp.auth.events"/>
|
||||
<property name="audit.kafka.file.download.topic" value="ervu.lkrp.import.file"/>
|
||||
<property name="audit.kafka.file.upload.topic" value="ervu.lkrp.download.request"/>
|
||||
<property name="audit.kafka.action.topic" value="ervu.lkrp.action.events"/>
|
||||
<property name="audit.kafka.security.protocol" value="SASL_PLAINTEXT"/>
|
||||
<property name="audit.kafka.doc.login.module" value="org.apache.kafka.common.security.scram.ScramLoginModule"/>
|
||||
<property name="audit.kafka.sasl.mechanism" value="SCRAM-SHA-256"/>
|
||||
<property name="audit.kafka.username" value="user1"/>
|
||||
<property name="audit.kafka.password" value="Blfi9d2OFG"/>
|
||||
<property name="audit.kafka.enabled" value="false"/>
|
||||
</system-properties>
|
||||
<management>
|
||||
<audit-log>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ru.micord.ervu.lkrp</groupId>
|
||||
<artifactId>ul</artifactId>
|
||||
<version>1.9.8-SNAPSHOT</version>
|
||||
<version>1.9.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>ru.micord.ervu.lkrp.ul</groupId>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
12
frontend/package-lock.json
generated
12
frontend/package-lock.json
generated
|
|
@ -1748,9 +1748,9 @@
|
|||
}
|
||||
},
|
||||
"@webbpm/base-package": {
|
||||
"version": "3.187.2",
|
||||
"resolved": "https://repo.micord.ru/repository/npm-all/@webbpm/base-package/-/base-package-3.187.2.tgz",
|
||||
"integrity": "sha512-qDW+Yjm/gyTIM/4N7uQasQR1zk2tGGAF6rJFpSUSb1A7PYreXPqSAShzWJJJ1YZ9CCz2dAXSQzm6JjUJKu2VUg==",
|
||||
"version": "3.187.3",
|
||||
"resolved": "https://repo.micord.ru/repository/npm-all/@webbpm/base-package/-/base-package-3.187.3.tgz",
|
||||
"integrity": "sha512-uhESrMdBnxeWXX5LNENvzzq0k0t4jHcuD1JmRAF0WIFfKqhZNMsmd0xxf2CYMUSJrWESyjX2wiqpKFhDHd0+/A==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
|
|
@ -2462,9 +2462,9 @@
|
|||
}
|
||||
},
|
||||
"cadesplugin_api": {
|
||||
"version": "2.0.4-micord.1",
|
||||
"resolved": "https://repo.micord.ru/repository/npm-all/cadesplugin_api/-/cadesplugin_api-2.0.4-micord.1.tgz",
|
||||
"integrity": "sha512-FyGVi1VWIyJOW1zOOQN0IkTH/Z/8g7pNWH7A71nf0h21FCX9SacUfgRwID+gl+NlpYiT3m+yZGdlEJsiDeV8JA=="
|
||||
"version": "2.1.1-micord.2",
|
||||
"resolved": "https://repo.micord.ru/repository/npm-all/cadesplugin_api/-/cadesplugin_api-2.1.1-micord.2.tgz",
|
||||
"integrity": "sha512-+j8RfbL7t2YMlSOC9Oa6+NoNLMYC3ZHkc9W6JQnV5+NBUqKLPAlLL1DF6llmW8coRdmgH6nZU8skvdk6M6qaBg=="
|
||||
},
|
||||
"calendar-utils": {
|
||||
"version": "0.8.5",
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"@angular/platform-browser-dynamic": "7.2.15",
|
||||
"@angular/router": "7.2.15",
|
||||
"@ng-bootstrap/ng-bootstrap": "4.2.2-micord.1",
|
||||
"@webbpm/base-package": "3.187.2",
|
||||
"@webbpm/base-package": "3.187.3",
|
||||
"ag-grid-angular": "29.0.0-micord.4",
|
||||
"ag-grid-community": "29.0.0-micord.4",
|
||||
"angular-calendar": "0.28.28",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ru.micord.ervu.lkrp</groupId>
|
||||
<artifactId>ul</artifactId>
|
||||
<version>1.9.8-SNAPSHOT</version>
|
||||
<version>1.9.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>ru.micord.ervu.lkrp.ul</groupId>
|
||||
|
|
|
|||
|
|
@ -16,5 +16,6 @@
|
|||
"password_pattern": "^((?=(.*\\d){1,})(?=.*[a-zа-яё])(?=.*[A-ZА-ЯЁ]).{8,})$",
|
||||
"password_pattern_error": "Пароль должен содержать заглавные или прописные буквы и как минимум 1 цифру",
|
||||
"show.client.errors": false,
|
||||
"available_task.single_fetch": true
|
||||
"available_task.single_fetch": true,
|
||||
"unknown.error.msg": "Система временно недоступна. Пожалуйста, повторите попытку позже."
|
||||
}
|
||||
|
|
|
|||
61
frontend/src/ts/ervu/LinkClickHandler.ts
Normal file
61
frontend/src/ts/ervu/LinkClickHandler.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import {AnalyticalScope, Behavior, Control, NotNull} from "@webbpm/base-package";
|
||||
import {AuditService} from "./service/AuditService";
|
||||
import {ElementRef, Input} from "@angular/core";
|
||||
import {LinkEventTypeEnum} from "./component/enum/LinkEventTypeEnum";
|
||||
|
||||
@AnalyticalScope(Control)
|
||||
export class LinkClickHandler extends Behavior {
|
||||
@Input()
|
||||
@NotNull()
|
||||
public eventType: LinkEventTypeEnum;
|
||||
private control: Control;
|
||||
private auditService: AuditService;
|
||||
private el: ElementRef
|
||||
|
||||
public initialize() {
|
||||
super.initialize();
|
||||
this.control = this.getScript(Control);
|
||||
this.injector.get(AuditService);
|
||||
this.auditService = this.injector.get(AuditService);
|
||||
this.el = this.control.getEl();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
super.bindEvents();
|
||||
if (this.el) {
|
||||
this.el.nativeElement.addEventListener('click',
|
||||
(event: MouseEvent) => this.onClickFunction(event));
|
||||
}
|
||||
}
|
||||
|
||||
unbindEvents() {
|
||||
super.unbindEvents();
|
||||
if (this.el) {
|
||||
this.el.nativeElement.removeEventListener('click',
|
||||
(event: MouseEvent) => this.onClickFunction(event));
|
||||
}
|
||||
}
|
||||
|
||||
private onClickFunction(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.tagName === 'A') {
|
||||
if (this.eventType === LinkEventTypeEnum.DOWNLOAD_EXAMPLE
|
||||
|| this.eventType === LinkEventTypeEnum.DOWNLOAD_TEMPLATE) {
|
||||
const href = target.getAttribute('href');
|
||||
if (href) {
|
||||
const fileName = this.extractFileNameFromHref(href);
|
||||
this.auditService.logActionAudit(this.eventType, null, fileName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.auditService.logActionAudit(this.eventType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extractFileNameFromHref(href: string): string {
|
||||
const parts = href.split('/');
|
||||
const fileNameWithExtension = parts[parts.length - 1];
|
||||
return fileNameWithExtension.split('?')[0];
|
||||
}
|
||||
}
|
||||
5
frontend/src/ts/ervu/component/enum/LinkEventTypeEnum.ts
Normal file
5
frontend/src/ts/ervu/component/enum/LinkEventTypeEnum.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export enum LinkEventTypeEnum {
|
||||
NAVIGATION_TO_SOURCE = "Переход на другие источники",
|
||||
DOWNLOAD_TEMPLATE = "Скачивание шаблона",
|
||||
DOWNLOAD_EXAMPLE = "Скачивание примера заполнения формы"
|
||||
}
|
||||
|
|
@ -4,11 +4,14 @@ import {
|
|||
GridRow,
|
||||
GridRowModelType,
|
||||
GridV2,
|
||||
GridV2Column, Visible
|
||||
GridV2Column,
|
||||
Visible
|
||||
} from "@webbpm/base-package";
|
||||
import {ChangeDetectionStrategy, Component} from "@angular/core";
|
||||
import {
|
||||
ColDef, FilterChangedEvent,
|
||||
ColDef,
|
||||
GridReadyEvent,
|
||||
FilterChangedEvent,
|
||||
ICellRendererParams,
|
||||
ITooltipParams,
|
||||
ValueFormatterParams,
|
||||
|
|
@ -17,6 +20,9 @@ import {
|
|||
import {StaticColumnInitializer} from "./StaticColumnInitializer";
|
||||
import {InMemoryStaticGridRpcService} from "../../../generated/ru/micord/ervu/service/rpc/InMemoryStaticGridRpcService";
|
||||
import {StaticGridColumn} from "../../../generated/ru/micord/ervu/property/grid/StaticGridColumn";
|
||||
import { FilterService } from "../../service/FilterService";
|
||||
import {AuditConstants, AuditService, FilterInfo} from "../../service/AuditService";
|
||||
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
|
|
@ -30,6 +36,7 @@ export class InMemoryStaticGrid extends GridV2 {
|
|||
public columnFiltersChanged: Event<any> = new Event<any>();
|
||||
|
||||
private rpcService: InMemoryStaticGridRpcService;
|
||||
private auditService: AuditService;
|
||||
|
||||
getRowModelType(): string {
|
||||
return GridRowModelType.CLIENT_SIDE;
|
||||
|
|
@ -37,6 +44,7 @@ export class InMemoryStaticGrid extends GridV2 {
|
|||
|
||||
protected initGrid() {
|
||||
super.initGrid();
|
||||
this.auditService = this.injector.get(AuditService);
|
||||
this.rpcService = this.getScript(InMemoryStaticGridRpcService);
|
||||
if (this.rpcService) {
|
||||
this.rpcService.loadData().then(response => {
|
||||
|
|
@ -47,6 +55,39 @@ export class InMemoryStaticGrid extends GridV2 {
|
|||
}
|
||||
}
|
||||
|
||||
onGridReady(event: GridReadyEvent) {
|
||||
super.onGridReady(event);
|
||||
this.addColumnFilterChangeListener(() => {
|
||||
this.auditActiveFilters();
|
||||
})
|
||||
}
|
||||
|
||||
private auditActiveFilters() {
|
||||
const filterModel = this.gridApi.getFilterModel();
|
||||
if (!filterModel || Object.keys(filterModel).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterMap: Record<string, FilterInfo> = {};
|
||||
Object.entries(filterModel).forEach(([column, agFilter]) => {
|
||||
const columnDef = this.gridApi.getColumnDef(column);
|
||||
if (!columnDef) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = FilterService.getFilterData(columnDef, agFilter);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
filterMap[columnDef.headerName] = data;
|
||||
});
|
||||
|
||||
if (Object.keys(filterMap).length > 0) {
|
||||
this.auditService.logActionAudit(AuditConstants.FILTER_EVENT, filterMap);
|
||||
}
|
||||
}
|
||||
|
||||
getColumns(): any[] {
|
||||
return this.getScriptsInChildren(GridV2Column)
|
||||
.map(columnV2 => columnV2.getScript(StaticGridColumn));
|
||||
|
|
|
|||
54
frontend/src/ts/ervu/service/AuditService.ts
Normal file
54
frontend/src/ts/ervu/service/AuditService.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import {Injectable} from "@angular/core";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Router} from "@angular/router";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root"
|
||||
})
|
||||
export class AuditService {
|
||||
constructor(private httpClient: HttpClient, private router: Router) {
|
||||
}
|
||||
|
||||
public logActionAudit(eventType: string, filterInfo?: Record<string, FilterInfo>,
|
||||
fileName?: string): void {
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const route = this.router.url;
|
||||
const sourceUrl = window.location.href;
|
||||
const auditEvent: AuditAction = {
|
||||
eventType: eventType,
|
||||
route: route,
|
||||
sourceUrl: sourceUrl,
|
||||
filterInfo: filterInfo,
|
||||
fileName: fileName
|
||||
}
|
||||
this.httpClient.post("audit/action", auditEvent, {
|
||||
headers: {
|
||||
"Client-Time-Zone": timeZone,
|
||||
}
|
||||
}).toPromise();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AuditConstants {
|
||||
public static readonly OPEN_PAGE_EVENT = "Открытие страницы";
|
||||
public static readonly FILTER_EVENT = "Поиск по фильтру";
|
||||
}
|
||||
|
||||
export interface AuditAction {
|
||||
eventType: string;
|
||||
route: string;
|
||||
sourceUrl: string;
|
||||
filterInfo?: Record<string, FilterInfo>;
|
||||
fileName?: string
|
||||
}
|
||||
|
||||
export interface FilterInfo {
|
||||
conditionOperator?: string;
|
||||
conditions: FilterCondition[];
|
||||
}
|
||||
|
||||
export interface FilterCondition {
|
||||
filterValue: string;
|
||||
filterType: string;
|
||||
}
|
||||
119
frontend/src/ts/ervu/service/FilterService.ts
Normal file
119
frontend/src/ts/ervu/service/FilterService.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import {DateFilter, NumberFilter, TextFilter} from "ag-grid-community";
|
||||
import {SetFilter} from "../component/grid/filter/SetFilter";
|
||||
import {FilterInfo} from "./AuditService";
|
||||
|
||||
export class FilterService {
|
||||
static getFilterData(columnDef: any, agFilter: any): FilterInfo {
|
||||
if (!agFilter) {
|
||||
return;
|
||||
}
|
||||
switch (columnDef.filter) {
|
||||
case DateFilter:
|
||||
case NumberFilter:
|
||||
return this.processDateOrNumberFilter(agFilter);
|
||||
case SetFilter:
|
||||
return this.processSetFilter(agFilter);
|
||||
case TextFilter:
|
||||
return this.processTextFilter(agFilter);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static processDateOrNumberFilter(agFilter: any): FilterInfo {
|
||||
if (!agFilter.condition1 && !agFilter.condition2) {
|
||||
if (agFilter.type === "inRange") {
|
||||
return this.createSingleConditionData(
|
||||
this.formatFilterValue(agFilter.dateFrom, agFilter.filter, agFilter.filterType),
|
||||
agFilter.type,
|
||||
this.formatFilterValue(agFilter.dateTo, agFilter.filterTo, agFilter.filterType)
|
||||
);
|
||||
}
|
||||
if (agFilter.type === "blank" || agFilter.type === "notBlank") {
|
||||
return this.createSingleConditionData(null, agFilter.type);
|
||||
}
|
||||
return this.createSingleConditionData(
|
||||
this.formatFilterValue(agFilter.dateFrom, agFilter.filter, agFilter.filterType),
|
||||
agFilter.type,
|
||||
);
|
||||
}
|
||||
return this.createDualConditionData(agFilter);
|
||||
}
|
||||
|
||||
private static processSetFilter(agFilter: any): FilterInfo {
|
||||
if (agFilter.value) {
|
||||
return this.createSingleConditionData(
|
||||
agFilter.value.join(", "),
|
||||
"in",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private static processTextFilter(agFilter: any): FilterInfo {
|
||||
if (!agFilter.condition1 && !agFilter.condition2) {
|
||||
if (agFilter.type === "blank" || agFilter.type === "notBlank") {
|
||||
return this.createSingleConditionData(null, agFilter.type);
|
||||
}
|
||||
return this.createSingleConditionData(
|
||||
agFilter.filter,
|
||||
agFilter.type
|
||||
);
|
||||
}
|
||||
return this.createDualConditionData(agFilter);
|
||||
}
|
||||
|
||||
private static createSingleConditionData(
|
||||
filterValue: string,
|
||||
filterType: string,
|
||||
endValue?: string
|
||||
): FilterInfo {
|
||||
return {
|
||||
conditionOperator: undefined,
|
||||
conditions: [{
|
||||
filterValue: endValue ? `${filterValue} to ${endValue}` : filterValue,
|
||||
filterType: filterType,
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
private static createDualConditionData(agFilter: any): FilterInfo {
|
||||
const condition1 = agFilter.condition1
|
||||
? {
|
||||
filterValue: this.getConditionValue(agFilter.condition1),
|
||||
filterType: agFilter.condition1.type,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const condition2 = agFilter.condition2
|
||||
? {
|
||||
filterValue: this.getConditionValue(agFilter.condition2),
|
||||
filterType: agFilter.condition2.type,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
conditionOperator: agFilter.operator,
|
||||
conditions: [condition1, condition2]
|
||||
};
|
||||
}
|
||||
|
||||
private static getConditionValue(condition: any): string {
|
||||
if (condition.type === "inRange") {
|
||||
return `${this.formatFilterValue(condition.dateFrom, condition.filter, condition.filterType)}
|
||||
to ${this.formatFilterValue(condition.dateTo, condition.filterTo, condition.filterType)}`;
|
||||
}
|
||||
if (condition.type === "blank" || condition.type === "notBlank") {
|
||||
return null;
|
||||
}
|
||||
return this.formatFilterValue(condition.dateFrom, condition.filter, condition.filterType);
|
||||
}
|
||||
|
||||
private static formatFilterValue(dateValue: any, defaultValue: any, filterType: string): string {
|
||||
if (filterType === "date" && dateValue) {
|
||||
return new Date(dateValue).toISOString();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ import {InMemoryStaticGrid} from "../../ervu/component/grid/InMemoryStaticGrid";
|
|||
import {ErvuDownloadFileButton} from "../../ervu/component/button/ErvuDownloadFileButton";
|
||||
import {AuthenticationService} from "../security/authentication.service";
|
||||
import {HomeLandingComponent} from "./component/home-landing.component";
|
||||
import {AuditService} from "../../ervu/service/AuditService";
|
||||
|
||||
registerLocaleData(localeRu);
|
||||
export const DIRECTIVES = [
|
||||
|
|
@ -67,7 +68,7 @@ export function checkAuthentication(authService: AuthenticationService): () => P
|
|||
DIRECTIVES
|
||||
],
|
||||
providers: [
|
||||
AuthenticationService,
|
||||
AuthenticationService, AuditService,
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: checkAuthentication,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {Injectable} from '@angular/core';
|
|||
import {HttpClient} from '@angular/common/http';
|
||||
import {CookieService} from "ngx-cookie";
|
||||
import {AppConfigService} from "@webbpm/base-package";
|
||||
import {map, tap} from "rxjs/operators";
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class AuthenticationService {
|
||||
|
|
@ -24,4 +25,13 @@ export class AuthenticationService {
|
|||
public isAuthenticated(): boolean {
|
||||
return this.cookieService.get('webbpm.ervu-lkrp-ul') != null;
|
||||
}
|
||||
|
||||
public redirectToEsia() {
|
||||
return this.http.get<string>("esia/url").pipe(
|
||||
tap(url => {
|
||||
window.open(url, "_self");
|
||||
}),
|
||||
map(() => true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export abstract class AuthGuard implements CanActivate {
|
|||
}
|
||||
if (error) {
|
||||
let errorMessage =
|
||||
'Произошла неизвестная ошибка. Обратитесь к системному администратору';
|
||||
this.messageService.getUnknowErrorMessage();
|
||||
let errorCode = this.extractCode(errorDescription);
|
||||
if (errorCode) {
|
||||
errorMessage = EsiaErrorDetail.getDescription(errorCode);
|
||||
|
|
@ -74,15 +74,10 @@ export abstract class AuthGuard implements CanActivate {
|
|||
return false;
|
||||
}
|
||||
else {
|
||||
return this.httpClient.get<string>("esia/url")
|
||||
.toPromise()
|
||||
.then(url => {
|
||||
window.open(url, "_self");
|
||||
return true;
|
||||
}).catch((reason) => {
|
||||
console.error(reason);
|
||||
return false;
|
||||
});
|
||||
return this.authenticationService.redirectToEsia().toPromise().catch((reason) => {
|
||||
console.error(reason);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}).catch((reason) => {
|
||||
if (reason !== 'Navigation on /home') {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
Router
|
||||
} from "@angular/router";
|
||||
import {ProgressIndicationService} from "@webbpm/base-package";
|
||||
import {AuditConstants, AuditService} from "../../../ervu/service/AuditService";
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
|
|
@ -21,7 +22,8 @@ export class WebbpmComponent {
|
|||
|
||||
constructor(private router: Router,
|
||||
private progressIndicationService: ProgressIndicationService,
|
||||
private cd: ChangeDetectorRef) {
|
||||
private cd: ChangeDetectorRef,
|
||||
private auditService:AuditService) {
|
||||
router.events.subscribe((event: Event) => {
|
||||
if (event instanceof NavigationStart) {
|
||||
progressIndicationService.showProgressBar();
|
||||
|
|
@ -29,9 +31,15 @@ export class WebbpmComponent {
|
|||
this.cd.markForCheck();
|
||||
}
|
||||
else if (event instanceof NavigationEnd
|
||||
|| event instanceof NavigationError
|
||||
|| event instanceof NavigationCancel) {
|
||||
|| event instanceof NavigationError
|
||||
|| event instanceof NavigationCancel) {
|
||||
progressIndicationService.hideProgressBar();
|
||||
|
||||
if (event instanceof NavigationEnd
|
||||
&& event.url != '/home'
|
||||
&& event.url != '/access-denied') {
|
||||
this.auditService.logActionAudit(AuditConstants.OPEN_PAGE_EVENT);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import {HTTP_INTERCEPTORS} from "@angular/common/http";
|
||||
import {
|
||||
FormDirtyInterceptor,
|
||||
HttpSecurityErrorInterceptor,
|
||||
HttpSecurityInterceptor
|
||||
} from "@webbpm/base-package";
|
||||
import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor";
|
||||
import {ErvuHttpSecurityErrorInterceptor} from "./ervu-http-security-error-interceptor";
|
||||
|
||||
export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [
|
||||
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true},
|
||||
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true},
|
||||
{provide: HTTP_INTERCEPTORS, useClass: ErvuHttpSecurityErrorInterceptor, multi: true},
|
||||
{provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true},
|
||||
{provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import {HTTP_INTERCEPTORS} from "@angular/common/http";
|
|||
import {FormDirtyInterceptor, HttpSecurityInterceptor} from "@webbpm/base-package";
|
||||
import {DevHttpSecurityErrorInterceptor} from "./http-security-error-interceptor.dev";
|
||||
import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor";
|
||||
import {ErvuHttpSecurityErrorInterceptor} from "./ervu-http-security-error-interceptor";
|
||||
|
||||
export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [
|
||||
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true},
|
||||
{provide: HTTP_INTERCEPTORS, useClass: DevHttpSecurityErrorInterceptor, multi: true},
|
||||
{provide: HTTP_INTERCEPTORS, useClass: ErvuHttpSecurityErrorInterceptor, multi: true},
|
||||
{provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true},
|
||||
{provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import {
|
||||
HttpEvent,
|
||||
HttpHandler,
|
||||
HttpInterceptor,
|
||||
HttpRequest, HttpResponse
|
||||
} from "@angular/common/http";
|
||||
import {
|
||||
HttpSecurityErrorInterceptor,
|
||||
MessagesService,
|
||||
UserService
|
||||
} from "@webbpm/base-package";
|
||||
import {Injectable} from "@angular/core";
|
||||
import {Router} from "@angular/router";
|
||||
import {from, Observable} from "rxjs";
|
||||
import {catchError, map} from "rxjs/operators";
|
||||
import {AuthenticationService} from "../../security/authentication.service";
|
||||
|
||||
@Injectable()
|
||||
export class ErvuHttpSecurityErrorInterceptor extends HttpSecurityErrorInterceptor
|
||||
implements HttpInterceptor {
|
||||
private authService: AuthenticationService;
|
||||
|
||||
|
||||
constructor(router: Router, messagesService: MessagesService, userService: UserService,
|
||||
authService: AuthenticationService) {
|
||||
super(router, messagesService, userService);
|
||||
this.authService = authService;
|
||||
}
|
||||
|
||||
protected processAuthError(req: HttpRequest<any>, next: HttpHandler,
|
||||
error: any): Observable<HttpEvent<any>> {
|
||||
if (this.authService.isAuthenticated()) {
|
||||
return super.processAuthError(req, next, error);
|
||||
}
|
||||
else {
|
||||
return from(this.authService.redirectToEsia()).pipe(
|
||||
map(() => new HttpResponse<any>()),
|
||||
catchError((err) => {
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
|
||||
import {HttpSecurityErrorInterceptor, MessagesService, UserService} from "@webbpm/base-package";
|
||||
import {MessagesService, UserService} from "@webbpm/base-package";
|
||||
import {Injectable} from "@angular/core";
|
||||
import {Router} from "@angular/router";
|
||||
import {EMPTY, Observable} from "rxjs";
|
||||
import {catchError} from "rxjs/operators";
|
||||
import {ErvuHttpSecurityErrorInterceptor} from "./ervu-http-security-error-interceptor";
|
||||
import {AuthenticationService} from "../../security/authentication.service";
|
||||
|
||||
@Injectable()
|
||||
export class DevHttpSecurityErrorInterceptor extends HttpSecurityErrorInterceptor
|
||||
export class DevHttpSecurityErrorInterceptor extends ErvuHttpSecurityErrorInterceptor
|
||||
implements HttpInterceptor {
|
||||
private router: Router;
|
||||
|
||||
|
||||
constructor(router: Router, messagesService: MessagesService, userService: UserService) {
|
||||
super(router, messagesService, userService);
|
||||
this.router = router;
|
||||
constructor(router: Router, messagesService: MessagesService, userService: UserService,
|
||||
authService: AuthenticationService) {
|
||||
super(router, messagesService, userService, authService);
|
||||
}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>ru.cg.webbpm.packages.base</groupId>
|
||||
<artifactId>resources</artifactId>
|
||||
<version>3.187.2</version>
|
||||
<version>3.187.3</version>
|
||||
<organization>
|
||||
<name>Micord</name>
|
||||
</organization>
|
||||
|
|
@ -28,13 +28,13 @@
|
|||
<jooq.version>3.19.3</jooq.version>
|
||||
<jupiter.version>5.10.2</jupiter.version>
|
||||
<enforcer.manageVersions>true</enforcer.manageVersions>
|
||||
<webbpm-platform.version>3.187.2</webbpm-platform.version>
|
||||
<webbpm-platform.version>3.187.3</webbpm-platform.version>
|
||||
<h2.version>1.4.200</h2.version>
|
||||
<build.timestamp>0115092226</build.timestamp>
|
||||
<build.timestamp>0224072420</build.timestamp>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<junit.platform.version>1.10.0</junit.platform.version>
|
||||
<enforcer.manageExclusions>true</enforcer.manageExclusions>
|
||||
<revision>3.187.2</revision>
|
||||
<revision>3.187.3</revision>
|
||||
<metadata.ts.filename>typescript.metadata.json</metadata.ts.filename>
|
||||
<package.repository.url>https://repo.micord.ru</package.repository.url>
|
||||
<maven.build.timestamp.format>MMddHHmmss</maven.build.timestamp.format>
|
||||
|
|
@ -47,19 +47,19 @@
|
|||
<dependency>
|
||||
<groupId>ru.cg.webbpm.packages.base</groupId>
|
||||
<artifactId>converters</artifactId>
|
||||
<version>3.187.2</version>
|
||||
<version>3.187.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.cg.webbpm.packages.base</groupId>
|
||||
<artifactId>backend</artifactId>
|
||||
<version>3.187.2</version>
|
||||
<version>3.187.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.cg.webbpm.packages.base</groupId>
|
||||
<artifactId>frontend</artifactId>
|
||||
<version>3.187.2</version>
|
||||
<version>3.187.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
<ul>
|
||||
|
||||
<li>Образец внешней ссылки: <code>https://www.wildberries.ru/catalog/${sku}/detail.aspx</code></li>
|
||||
<li>Образец внутренней ссылки: <code>products/ru.cg.webbpm.packages.base:resources:jar:3.187.2</code></li>
|
||||
<li>Образец внутренней ссылки: <code>products/ru.cg.webbpm.packages.base:resources:jar:3.187.3</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -4,17 +4,17 @@
|
|||
<description>Base webbpm package</description>
|
||||
<groupId>ru.cg.webbpm.packages.base</groupId>
|
||||
<artifactId>resources</artifactId>
|
||||
<version>3.187.2</version>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<version>3.187.3</version>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
|
||||
<backendModule>
|
||||
<groupId>ru.cg.webbpm.packages.base</groupId>
|
||||
<artifactId>backend</artifactId>
|
||||
<version>3.187.2</version>
|
||||
<version>3.187.3</version>
|
||||
</backendModule>
|
||||
<frontendModule>
|
||||
<packageName>@webbpm/base-package</packageName>
|
||||
<version>3.187.2</version>
|
||||
<version>3.187.3</version>
|
||||
</frontendModule>
|
||||
</packageInfo>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_отмены.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_очистки_фильтра.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_удаления.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_загрузки.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_вызова_ошибки.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
<documentation>component/buttons/Кнопка_выполнения_бизнес-процесса.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_выполнения_SQL.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_для_фильтрации.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_навигации.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_сохранения.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_выбора.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_подписи.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/Кнопка_запуска_бизнес-процесса.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/reporting/Кнопка_печати_из_графа_сущности.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/buttons/reporting/Кнопка_печати_отчета_из_формы.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/containers/Сворачиваемая_панель.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/containers/Диалог.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/containers/Контейнер_с_кнопками.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/containers/Группа_полей.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
<documentation>component/containers/Набор_фильтров.html</documentation>
|
||||
<internal>false</internal>
|
||||
<versions>
|
||||
<studioVersion>3.187.2</studioVersion>
|
||||
<studioVersion>3.187.3</studioVersion>
|
||||
<packageVersions>
|
||||
<entry>
|
||||
<key>ru.cg.webbpm.packages.base.resources</key>
|
||||
<value>3.187.2</value>
|
||||
<value>3.187.3</value>
|
||||
</entry>
|
||||
</packageVersions>
|
||||
</versions>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue