Merge branch 'hotfix/1.9.5'

This commit is contained in:
Zaripov Emil 2025-01-16 10:11:33 +03:00
commit 714374ae55
156 changed files with 1015 additions and 469 deletions

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId>
<version>1.9.4</version>
<version>1.9.5-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.ul</groupId>
<artifactId>backend</artifactId>
@ -89,6 +89,14 @@
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<dependency>
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.modules</groupId>
<artifactId>inject</artifactId>
@ -182,6 +190,10 @@
<groupId>com.github.lookfirst</groupId>
<artifactId>sardine</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.parent.artifactId}</finalName>

View file

@ -3,6 +3,7 @@ package ervu.client.fileupload;
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.ConnectException;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.net.URLEncoder;
@ -10,14 +11,27 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import javax.annotation.PostConstruct;
import com.github.sardine.DavResource;
import com.github.sardine.Sardine;
import com.github.sardine.SardineFactory;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import ervu.exception.WebDavException;
import ervu.model.webdav.Server;
import org.apache.http.client.ClientProtocolException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@ -36,34 +50,113 @@ import org.springframework.web.multipart.MultipartFile;
*/
@Component
public class WebDavClient {
private static final Logger logger = LoggerFactory.getLogger(WebDavClient.class);
private static final Logger LOGGER = LoggerFactory.getLogger(WebDavClient.class);
@Value("${file.webdav.upload.username}")
@Value("${webdav.urls}")
private String[] urls;
@Value("${webdav.username}")
private String username;
@Value("${file.webdav.upload.password}")
@Value("${webdav.password}")
private String password;
@Value("${webdav.bad_servers.cache.expire.seconds:120}")
private long cacheExpireSec;
private List<Server> servers;
private LoadingCache<String, String> badServersCache;
private final AtomicInteger counter = new AtomicInteger(0);
@PostConstruct
public void init() {
CacheLoader<String, String> loader = new CacheLoader<>() {
@Override
public String load(String key) {
return key;
}
};
badServersCache = CacheBuilder.newBuilder()
.expireAfterAccess(cacheExpireSec, TimeUnit.SECONDS)
.build(loader);
LOGGER.info("WebDAV urls: {}", Arrays.asList(urls));
servers = Arrays.stream(urls)
.map(url -> new Server(url, ZonedDateTime.now().toInstant().toEpochMilli()))
.toList();
}
@Retryable(value = {IOException.class}, backoff = @Backoff(delayExpression = "${webdav.retry.delay:500}"))
public boolean uploadFile(String url, MultipartFile multipartFile) {
public String uploadFile(MultipartFile multipartFile) {
String fileName = getNewFilename(multipartFile.getOriginalFilename());
Sardine sardine = initClient(username, password);
try {
sardine.put(url, multipartFile.getBytes());
return sardine.exists(url);
return putAndGetUrl(multipartFile.getBytes(), fileName, sardine);
}
catch (IOException e) {
throw new RuntimeException("Failed to put or check file in WebDAV", e);
throw new WebDavException("Failed to put file into WebDAV", e);
}
finally {
try {
sardine.shutdown();
}
catch (IOException e) {
logger.error("Failed to shutdown WebDAV client", e);
LOGGER.error("Failed to shutdown WebDAV client", e);
}
}
}
private String getNewFilename(String filename) {
int lastIndexOf = filename.lastIndexOf(".");
String fileExtension = lastIndexOf == -1 ? "" : filename.substring(lastIndexOf);
return UUID.randomUUID() + fileExtension;
}
public String putAndGetUrl(byte[] fileBytes, String fileName, Sardine client) throws IOException {
if (badServersCache.size() == urls.length) {
return null;
}
Server server;
if (urls.length == 1) {
server = servers.get(0);
}
else {
Predicate<Server> isNotBad = s -> badServersCache.getIfPresent(s.getUrl()) == null;
server = servers.stream()
.filter(isNotBad.and(s -> servers.indexOf(s) == index()))
.findFirst()
.orElseGet(() -> servers.stream()
.filter(isNotBad)
.min(Comparator.comparing(Server::getLastCallTime))
.orElse(null));
}
if (server == null) {
return null;
}
boolean isBad = false;
String serverUrl = server.getUrl();
String fileUploadUrl = serverUrl + "/" + fileName;
try {
client.put(fileUploadUrl, fileBytes);
server.setLastCallTime(System.currentTimeMillis());
}
catch (ConnectException | ClientProtocolException ignore) {
isBad = true;
}
if (isBad) {
badServersCache.getUnchecked(serverUrl);
return putAndGetUrl(fileBytes, fileName, client);
}
return fileUploadUrl;
}
private int index() {
counter.compareAndSet(Integer.MAX_VALUE, 0);
return counter.getAndIncrement() % urls.length;
}
@Retryable(value = {IOException.class, InterruptedException.class},
backoff = @Backoff(delayExpression = "${webdav.retry.delay:500}"))
public ResponseEntity<Resource> webDavDownloadFile(String url) {
@ -94,12 +187,12 @@ public class WebDavClient {
.body(resource);
}
else {
logger.error("Failed to download file: HTTP status code {}", response.statusCode());
LOGGER.error("Failed to download file: HTTP status code {}", response.statusCode());
return ResponseEntity.status(response.statusCode()).build();
}
}
catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
throw new WebDavException(e);
}
}
@ -109,8 +202,7 @@ public class WebDavClient {
}
@Retryable(value = {IOException.class}, backoff = @Backoff(delayExpression = "${webdav.retry.delay:500}"))
public void deleteFilesOlderThan(long seconds, String url, String username, String password,
String... extensions) {
public void deleteFilesOlderThan(long seconds, String url, String... extensions) throws IOException {
Sardine sardine = initClient(username, password);
try {
@ -124,20 +216,17 @@ public class WebDavClient {
sardine.delete(url + davResource.getPath());
}
catch (IOException e) {
throw new RuntimeException("Failed to delete file " + davResource.getName(), e);
LOGGER.error("Failed to delete file {}", davResource.getName(), e);
}
}
});
}
catch (IOException e) {
throw new RuntimeException("Failed to delete old files from WebDAV", e);
}
finally {
try {
sardine.shutdown();
}
catch (IOException e) {
logger.error("Failed to shutdown WebDAV client", e);
LOGGER.error("Failed to shutdown WebDAV client", e);
}
}
}

View file

@ -2,8 +2,6 @@ package ervu.controller;
import java.time.ZonedDateTime;
import java.util.TimeZone;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import ervu.service.fileupload.EmployeeInfoFileUploadService;
import org.springframework.http.ResponseEntity;
@ -13,6 +11,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
/**
* @author Alexandr Shalaginov
@ -26,26 +26,17 @@ public class EmployeeInfoFileUploadController {
}
@RequestMapping(value = "/employee/document", method = RequestMethod.POST)
public ResponseEntity<?> saveEmployeeInformationFile(@RequestParam("file") MultipartFile multipartFile,
public ResponseEntity<?> saveEmployeeInformationFile(
@RequestParam("file") MultipartFile multipartFile,
@RequestHeader("X-Employee-Info-File-Form-Type") String formType,
@RequestHeader("Client-Time-Zone") String clientTimeZone, HttpServletRequest request) {
String accessToken = null;
String authToken = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("auth_token")) {
authToken = cookie.getValue();
}
}
}
@RequestHeader("Client-Time-Zone") String clientTimeZone) {
UserIdsPair userIdsPair = SecurityUtil.getUserIdsPair();
if (authToken != null) {
if (userIdsPair != null) {
String offset = ZonedDateTime.now(TimeZone.getTimeZone(clientTimeZone).toZoneId())
.getOffset().getId();
if (this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType,
authToken, offset)) {
if (this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType, offset, userIdsPair)) {
return ResponseEntity.ok("File successfully uploaded.");
}
}

View file

@ -0,0 +1,19 @@
package ervu.exception;
/**
* @author Adel Kalimullin
*/
public class WebDavException extends RuntimeException{
public WebDavException(String message) {
super(message);
}
public WebDavException(String message, Throwable cause) {
super(message, cause);
}
public WebDavException(Throwable cause) {
super(cause);
}
}

View file

@ -0,0 +1,27 @@
package ervu.model.webdav;
/**
* @author gulnaz
*/
public class Server {
private final String url;
private long lastCallTime;
public Server(String url, long lastCallTime) {
this.url = url;
this.lastCallTime = lastCallTime;
}
public String getUrl() {
return url;
}
public long getLastCallTime() {
return lastCallTime;
}
public void setLastCallTime(long lastCallTime) {
this.lastCallTime = lastCallTime;
}
}

View file

@ -1,10 +1,12 @@
package ervu.service.fileupload;
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;
import com.fasterxml.jackson.core.JsonProcessingException;
@ -16,6 +18,11 @@ import ervu.model.fileupload.EmployeeInfoKafkaMessage;
import ervu.model.fileupload.FileInfo;
import ervu.model.fileupload.FileStatus;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.tika.Tika;
import org.apache.tika.mime.MediaType;
import org.apache.tika.mime.MimeType;
import org.apache.tika.mime.MimeTypeException;
import org.apache.tika.mime.MimeTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
@ -24,15 +31,18 @@ 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.security.esia.token.EsiaTokensStore;
import ru.micord.ervu.exception.JsonParsingException;
import ru.micord.ervu.security.esia.model.EmployeeModel;
import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.esia.service.UlDataService;
import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
import ru.micord.ervu.service.InteractionService;
import static ervu.enums.FileStatusCode.*;
import static ervu.enums.FileStatusCode.FILE_CLEAN;
import static ervu.enums.FileStatusCode.FILE_INFECTED;
import static ervu.enums.FileStatusCode.FILE_NOT_CHECKED;
import static ervu.enums.FileStatusCode.FILE_UPLOADED;
import static ru.micord.ervu.util.StringUtils.convertToFio;
/**
@ -40,7 +50,7 @@ import static ru.micord.ervu.util.StringUtils.convertToFio;
*/
@Service
public class EmployeeInfoFileUploadService {
private static final Logger logger = LoggerFactory.getLogger(EmployeeInfoFileUploadService.class);
private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeInfoFileUploadService.class);
private static final String FORMAT = "dd.MM.yyyy HH:mm:ss";
private final WebDavClient webDavClient;
@ -48,46 +58,50 @@ public class EmployeeInfoFileUploadService {
private final KafkaTemplate<String, String> kafkaTemplate;
private final InteractionService interactionService;
private final UlDataService ulDataService;
private final JwtTokenService jwtTokenService;
@Value("${av.kafka.message.topic.name}")
private String kafkaTopicName;
@Value("${file.webdav.upload.url:http://localhost:5757}")
private String url;
public EmployeeInfoFileUploadService(
WebDavClient webDavClient,
EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService,
@Qualifier("avTemplate") KafkaTemplate<String, String> kafkaTemplate,
InteractionService interactionService,
UlDataService ulDataService, JwtTokenService jwtTokenService) {
UlDataService ulDataService) {
this.webDavClient = webDavClient;
this.employeeInfoKafkaMessageService = employeeInfoKafkaMessageService;
this.kafkaTemplate = kafkaTemplate;
this.interactionService = interactionService;
this.ulDataService = ulDataService;
this.jwtTokenService = jwtTokenService;
}
public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType, String authToken, String offset) {
String fileUploadUrl = this.url + "/" + getNewFilename(multipartFile.getOriginalFilename());
LocalDateTime now = LocalDateTime.now();
public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType,
String offset, UserIdsPair userIdsPair) {
if (this.webDavClient.uploadFile(fileUploadUrl, multipartFile)) {
FileStatus fileStatus = new FileStatus();
fileStatus.setStatus("Загрузка");
if (!isValid(multipartFile)) {
return false;
}
String fileId = UUID.randomUUID().toString();
String fileName = multipartFile.getOriginalFilename();
EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(formType);
String esiaUserId = userIdsPair.getEsiaUserId();
String ervuId = userIdsPair.getErvuId();
String accessToken = EsiaTokensStore.getAccessToken(esiaUserId);
EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken);
PersonModel personModel = employeeModel.getPerson();
String fileUploadUrl = this.webDavClient.uploadFile(multipartFile);
FileStatus fileStatus = new FileStatus();
fileStatus.setStatus(fileUploadUrl == null ? "Невозможно проверить файл ЛК РП" : "Загрузка");
LocalDateTime now = LocalDateTime.now();
interactionService.setStatus(fileId, fileStatus.getStatus(), fileName,
employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now),
convertToFio(personModel.getFirstName(), personModel.getMiddleName(), personModel.getLastName()),
ervuId);
if (fileUploadUrl != null) {
fileStatus.setCode(FILE_UPLOADED.getCode());
fileStatus.setDescription("Файл принят до проверки на вирусы");
String fileId = UUID.randomUUID().toString();
String fileName = multipartFile.getOriginalFilename();
EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(formType);
Token token = jwtTokenService.getToken(authToken);
String[] ids = token.getUserAccountId().split(":");
String userId = ids[0];
String ervuId = ids[1];
String accessToken = EsiaTokensStore.getAccessToken(userId);
EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken);
PersonModel personModel = employeeModel.getPerson();
String departureDateTime = now.format(DateTimeFormatter.ofPattern(FORMAT));
String jsonMessage = getJsonKafkaMessage(
employeeInfoKafkaMessageService.getKafkaMessage(
@ -100,56 +114,74 @@ public class EmployeeInfoFileUploadService {
offset,
fileStatus,
ervuId,
userId,
esiaUserId,
personModel
)
)
);
interactionService.setStatus(fileId, fileStatus.getStatus(), fileName,
employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now),
convertToFio(personModel.getFirstName(), personModel.getMiddleName(), personModel.getLastName()),
ervuId);
return sendMessage(jsonMessage);
}
else {
logger.error("Fail upload file: {}", multipartFile.getOriginalFilename());
LOGGER.error("Failed to upload file: {}", fileName);
return false;
}
}
private boolean isValid(MultipartFile multipartFile) {
if (multipartFile == null || multipartFile.getOriginalFilename() == null) {
return false;
}
String fileName = multipartFile.getOriginalFilename();
try {
String contentType = new Tika().detect(multipartFile.getBytes());
MimeTypes defaultMimeTypes = MimeTypes.getDefaultMimeTypes();
MimeType mimeType = defaultMimeTypes.forName(contentType);
boolean isCsv = mimeType.getType().equals(MediaType.TEXT_PLAIN)
&& fileName.toLowerCase(Locale.getDefault()).endsWith(".csv");
if (!isCsv) {
LOGGER.info("Trying to upload file={} with wrong mime type={}",
fileName, mimeType
);
}
return isCsv;
}
catch (MimeTypeException e) {
LOGGER.error(
"Couldn't get mime type from bytes for file=" + fileName, e);
return false;
}
catch (IOException e) {
LOGGER.error("Error while checking file type, file=" + fileName,
e
);
return false;
}
}
private boolean sendMessage(String message) {
ProducerRecord<String, String> record = new ProducerRecord<>(this.kafkaTopicName, message);
record.headers().add("messageId", UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
record.headers()
.add("messageId", UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
try {
this.kafkaTemplate.send(record).get();
logger.debug("Success send record: {}", record);
LOGGER.debug("Successfully sent record: {}", record);
return true;
}
catch (Exception exception) {
logger.error("Fail send message.", exception);
LOGGER.error("Failed to send message", exception);
return false;
}
}
private String getNewFilename(String oldFilename) {
return UUID.randomUUID() + getFileExtension(oldFilename);
}
private String getFileExtension(String filename) {
int lastIndexOf = filename.lastIndexOf(".");
if (lastIndexOf == -1) {
return "";
}
return filename.substring(lastIndexOf);
}
private String getJsonKafkaMessage(EmployeeInfoKafkaMessage employeeInfoKafkaMessage) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(employeeInfoKafkaMessage);
}
catch (JsonProcessingException e) {
throw new RuntimeException(String.format("Fail get json from: %s", employeeInfoKafkaMessage), e);
throw new JsonParsingException(String.format("Fail get json from: %s", employeeInfoKafkaMessage), e);
}
}
@ -173,7 +205,7 @@ public class EmployeeInfoFileUploadService {
}
}
catch (JsonProcessingException e) {
throw new RuntimeException(String.format("Fail get json from: %s", kafkaMessage), e);
throw new JsonParsingException(String.format("Fail get json from: %s", kafkaMessage), e);
}
}
}

View file

@ -1,7 +1,12 @@
package ervu.service.scheduler;
import java.io.IOException;
import java.util.Arrays;
import ervu.client.fileupload.WebDavClient;
import net.javacrumbs.shedlock.core.SchedulerLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@ -11,15 +16,11 @@ import org.springframework.stereotype.Service;
*/
@Service
public class WebDavSchedulerService {
private static final Logger LOGGER = LoggerFactory.getLogger(WebDavSchedulerService.class);
private final WebDavClient webDavClient;
@Value("${file.webdav.upload.url}")
private String url;
@Value("${file.webdav.upload.username}")
private String username;
@Value("${file.webdav.upload.password}")
private String password;
@Value("${webdav.urls}")
private String[] urls;
@Value("${file.webdav.lifetime.seconds:300}")
private long fileLifetime;
@Value("${file.webdav.extensions:}")
@ -30,8 +31,15 @@ public class WebDavSchedulerService {
}
@Scheduled(cron = "${webdav.cleanup.cron:0 0 0 * * *}")
@SchedulerLock
@SchedulerLock(name = "webDavOldFilesCleaning")
public void deleteOldFiles() {
webDavClient.deleteFilesOlderThan(fileLifetime, url, username, password, fileExtensions);
Arrays.stream(urls).forEach(url -> {
try {
webDavClient.deleteFilesOlderThan(fileLifetime, url, fileExtensions);
}
catch (IOException e) {
LOGGER.error("Failed to clean up WebDAV on {}", url, e);
}
});
}
}

View file

@ -0,0 +1,19 @@
package ru.micord.ervu.exception;
/**
* @author Adel Kalimullin
*/
public class JsonParsingException extends RuntimeException {
public JsonParsingException(String message) {
super(message);
}
public JsonParsingException(String message, Throwable cause) {
super(message, cause);
}
public JsonParsingException(Throwable cause) {
super(cause);
}
}

View file

@ -0,0 +1,30 @@
package ru.micord.ervu.security;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(
MethodHandles.lookup().lookupClass());
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
if (response.isCommitted()) {
LOGGER.trace("Did not write to response since already committed");
return;
}
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("\"" + HttpStatus.FORBIDDEN.getReasonPhrase() + "\"");
}
}

View file

@ -9,7 +9,6 @@ import org.springframework.security.config.annotation.authentication.configurati
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
@ -25,7 +24,6 @@ import ru.micord.ervu.security.webbpm.jwt.JwtMatcher;
import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint;
import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter;
import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT;
@ -33,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"
"/version", "/esia/url", "/esia/auth", "esia/refresh", "/esia/logout",
};
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@ -51,7 +49,8 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http,
CookieCsrfTokenRepository tokenRepository)
CookieCsrfTokenRepository tokenRepository,
UnauthorizedEntryPoint entryPoint)
throws Exception {
XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
delegate.setCsrfRequestAttributeName(null);
@ -69,7 +68,8 @@ public class SecurityConfig {
.logout((logout) -> logout.logoutUrl(ESIA_LOGOUT)
.logoutSuccessHandler(new LogoutSuccessHandler(tokenRepository, esiaAuthService)))
.exceptionHandling()
.authenticationEntryPoint(entryPoint())
.authenticationEntryPoint(entryPoint)
.accessDeniedHandler(new AccessDeniedHandlerImpl())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
@ -88,8 +88,9 @@ public class SecurityConfig {
return tokenRepository;
}
public AuthenticationEntryPoint entryPoint() {
return new UnauthorizedEntryPoint();
@Bean
public UnauthorizedEntryPoint entryPoint(SecurityHelper securityHelper) {
return new UnauthorizedEntryPoint(securityHelper);
}
@Bean
@ -105,9 +106,10 @@ public class SecurityConfig {
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper,
AuthenticationManager manager) {
AuthenticationManager manager,
UnauthorizedEntryPoint entryPoint) {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(
new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper);
new JwtMatcher("/**", PERMIT_ALL), entryPoint, securityHelper);
jwtAuthenticationFilter.setAuthenticationManager(manager);
return jwtAuthenticationFilter;
}

View file

@ -31,9 +31,11 @@ public class EsiaController {
return esiaAuthService.generateAuthCodeUrl();
}
@GetMapping(value = "/esia/auth", params = "code")
public ResponseEntity<?> esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) {
return esiaAuthService.getEsiaTokensByCode(code, request, response);
@GetMapping(value = "/esia/auth")
public ResponseEntity<?> esiaAuth(@RequestParam(value = "code", required = false) String code,
@RequestParam(value = "error", required = false) String error, HttpServletRequest request,
HttpServletResponse response) {
return esiaAuthService.getEsiaTokensByCode(code, error, request, response);
}
@PostMapping(value = "/esia/refresh")

View file

@ -0,0 +1,19 @@
package ru.micord.ervu.security.esia.exception;
/**
* @author Adel Kalimullin
*/
public class EsiaException extends RuntimeException{
public EsiaException(String message, Throwable cause) {
super(message, cause);
}
public EsiaException(Throwable cause) {
super(cause);
}
public EsiaException(String message) {
super(message);
}
}

View file

@ -14,13 +14,14 @@ import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import ervu.service.okopf.OkopfService;
import org.springframework.security.authentication.AuthenticationManager;
import ru.micord.ervu.security.esia.exception.EsiaException;
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -50,6 +51,8 @@ import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import ru.micord.ervu.security.webbpm.jwt.model.Token;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.getCurrentUsername;
/**
* @author Eduard Tihomirov
*/
@ -70,9 +73,6 @@ public class EsiaAuthService {
private OkopfService okopfService;
@Autowired
private SecurityHelper securityHelper;
@Autowired
private AuthenticationManager authenticationManager;
@Value("${ervu.kafka.org.reply.topic}")
private String requestReplyTopic;
@ -122,7 +122,7 @@ public class EsiaAuthService {
return buildUrl(url, params);
}
catch (Exception e) {
throw new RuntimeException(e);
throw new EsiaException(e);
}
}
@ -152,7 +152,18 @@ public class EsiaAuthService {
return uriBuilder.toString();
}
public ResponseEntity<?> getEsiaTokensByCode(String esiaAuthCode, HttpServletRequest request, HttpServletResponse response) {
public ResponseEntity<?> getEsiaTokensByCode(String esiaAuthCode, String error,
HttpServletRequest request, HttpServletResponse response) {
if (error != null && !error.equals("null")) {
return new ResponseEntity<>(
"Произошла неизвестная ошибка. Обратитесь к системному администратору",
HttpStatus.FORBIDDEN
);
}
String esiaAccessTokenStr = null;
String prnOid = null;
Long expiresIn = null;
boolean hasRole = false;
try {
String clientId = esiaConfig.getClientId();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx");
@ -206,27 +217,21 @@ public class EsiaAuthService {
tokenResponse != null ? tokenResponse.getError_description() : "response is empty";
throw new IllegalStateException("Esia response error. " + errMsg);
}
String esiaAccessTokenStr = tokenResponse.getAccess_token();
esiaAccessTokenStr = tokenResponse.getAccess_token();
String esiaRefreshTokenStr = tokenResponse.getRefresh_token();
boolean hasRole = ulDataService.checkRole(esiaAccessTokenStr);
EsiaAccessToken esiaAccessToken = ulDataService.readToken(esiaAccessTokenStr);
String prnOid = esiaAccessToken.getSbj_id();
String ervuId = getErvuId(esiaAccessTokenStr, prnOid);
Long expiresIn = tokenResponse.getExpires_in();
prnOid = esiaAccessToken.getSbj_id();
expiresIn = tokenResponse.getExpires_in();
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
EsiaTokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn);
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, hasRole);
int expiry = tokenResponse.getExpires_in().intValue();
securityHelper.addAccessCookies(response,token.getValue(), expiry);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext();
JwtAuthentication jwtAuthentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
esiaAccessToken.getSbj_id(), token.getValue());
authenticationManager.authenticate(jwtAuthentication);
context.setAuthentication(jwtAuthentication);
SecurityContextHolder.setContext(context);
}
catch (Exception e) {
throw new EsiaException(e);
}
try {
hasRole = ulDataService.checkRole(esiaAccessTokenStr);
String ervuId = getErvuId(esiaAccessTokenStr, prnOid);
createTokenAndAddCookie(response, prnOid, ervuId, hasRole, expiresIn);
if (!hasRole) {
LOGGER.error("The user with id = " + prnOid + " does not have the required role");
return new ResponseEntity<>(
@ -237,7 +242,14 @@ public class EsiaAuthService {
return ResponseEntity.ok("Authentication successful");
}
catch (Exception e) {
throw new RuntimeException(e);
createTokenAndAddCookie(response, prnOid, null, hasRole , expiresIn);
String messageId = getMessageId(e);
String messageWithId = String.format("[%s] %s", messageId, e.getMessage());
LOGGER.error(messageWithId, e);
return new ResponseEntity<>(
"Произошла ошибка " + messageId + ". Обратитесь к системному администратору",
HttpStatus.FORBIDDEN
);
}
}
@ -302,20 +314,10 @@ public class EsiaAuthService {
EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
EsiaTokensStore.addRefreshToken(prnOid, esiaNewRefreshToken, expiresIn);
String ervuId = getErvuId(esiaAccessTokenStr, prnOid);
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, true);
int expiry = tokenResponse.getExpires_in().intValue();
securityHelper.addAccessCookies(response, token.getValue(), expiry);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext();
JwtAuthentication jwtAuthentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
esiaAccessToken.getSbj_id(), token.getValue());
authenticationManager.authenticate(jwtAuthentication);
context.setAuthentication(jwtAuthentication);
SecurityContextHolder.setContext(context);
createTokenAndAddCookie(response, esiaAccessToken.getSbj_id(), ervuId, true, expiresIn);
}
catch (Exception e) {
throw new RuntimeException(e);
throw new EsiaException(e);
}
}
@ -341,13 +343,13 @@ public class EsiaAuthService {
}
catch (Exception e) {
throw new RuntimeException(e);
throw new EsiaException(e);
}
}
private void errorHandler(HttpResponse<?> httpResponse) {
if (httpResponse.statusCode() != 200) {
throw new RuntimeException(httpResponse.statusCode() + " " + httpResponse.body());
throw new EsiaException(httpResponse.statusCode() + " " + httpResponse.body());
}
}
@ -366,7 +368,7 @@ public class EsiaAuthService {
return buildUrl(url, params);
}
catch (Exception e) {
throw new RuntimeException(e);
throw new EsiaException(e);
}
}
@ -384,12 +386,12 @@ public class EsiaAuthService {
String ervuId = ervuOrgResponse.getData().getOrgId_ERVU();
if (!StringUtils.hasText(ervuId)) {
throw new RuntimeException("No ervuId for prnOid = " + prnOid);
throw new EsiaException("No ervuId for prnOid = " + prnOid);
}
return ervuId;
}
catch (Exception e) {
throw new RuntimeException(e);
throw new EsiaException(e);
}
}
@ -448,4 +450,24 @@ public class EsiaAuthService {
employee.setOrgOid(employeeModel.getOrgOid());
return employee;
}
private String getMessageId(Exception exception) {
return Integer.toUnsignedString(Objects
.hashCode(getCurrentUsername()), 36)
+ "-"
+ Integer.toUnsignedString(exception.hashCode(), 36);
}
private void createTokenAndAddCookie(HttpServletResponse response, String userId, String ervuId,
Boolean hasRole, Long expiresIn) {
Token token = jwtTokenService.createAccessToken(userId, expiresIn, ervuId, hasRole);
securityHelper.addAccessCookies(response, token.getValue(), expiresIn.intValue());
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext();
JwtAuthentication authentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
userId, token.getValue());
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
}

View file

@ -17,8 +17,11 @@ public class JwtAuthentication implements Authentication {
private final Authentication authentication;
private final String token;
private final UserIdsPair userIdsPair;
public JwtAuthentication(Authentication authentication, String userAccountId, String token) {
this.userAccountId = userAccountId;
this.userIdsPair = new UserIdsPair(userAccountId);
this.authentication = authentication;
this.token = token;
}
@ -31,6 +34,10 @@ public class JwtAuthentication implements Authentication {
return userAccountId;
}
public UserIdsPair getUserIdsPair() {
return userIdsPair;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authentication.getAuthorities();

View file

@ -48,22 +48,20 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
throw new BadCredentialsException("Authentication Failed.", e);
}
if (jwtTokenService.isValid(token)) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
REFERENCE_REQUEST);
if (request == null) {
throw new IllegalStateException("No request found in request attributes");
}
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
REFERENCE_REQUEST);
if (request == null) {
throw new IllegalStateException("No request found in request attributes");
}
if (request.getRequestURI().endsWith("esia/logout") || token.getHasRole()) {
UsernamePasswordAuthenticationToken pwdToken =
UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null,
Collections.emptyList()
);
if (jwtTokenService.isValid(token) && token.getHasRole()) {
UsernamePasswordAuthenticationToken pwdToken =
UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null,
Collections.emptyList()
);
return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
}
return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
}
throw new BadCredentialsException(

View file

@ -6,12 +6,19 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper;
/**
* {@link AuthenticationEntryPoint} that rejects all requests with an unauthorized error message.
*/
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
private final SecurityHelper securityHelper;
public UnauthorizedEntryPoint(SecurityHelper securityHelper) {
this.securityHelper = securityHelper;
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException {
@ -21,9 +28,11 @@ public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
response.setStatus(HttpServletResponse.SC_OK);
}
else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Unauthorized: Authentication token was either missing or invalid."
);
securityHelper.clearAccessCookies(response);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter()
.write("\"Unauthorized: Authentication token was either missing or invalid.\"");
}
}
}

View file

@ -0,0 +1,27 @@
package ru.micord.ervu.security.webbpm.jwt;
public class UserIdsPair {
private final String esiaUserId;
private final String ervuId;
public UserIdsPair(String idsConcatenated) {
if (idsConcatenated == null) {
this.esiaUserId = null;
this.ervuId = null;
}
else {
String[] ids = idsConcatenated.split(":");
this.esiaUserId = ids[0];
this.ervuId = ids.length == 2 ? ids[1] : null;
}
}
public String getEsiaUserId() {
return esiaUserId;
}
public String getErvuId() {
return ervuId;
}
}

View file

@ -1,9 +1,15 @@
package ru.micord.ervu.security.webbpm.jwt.util;
import java.util.Optional;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.util.WebUtils;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
public final class SecurityUtil {
public static final String AUTH_TOKEN = "auth_token";
@ -18,4 +24,18 @@ public final class SecurityUtil {
Cookie cookie = WebUtils.getCookie(httpRequest, AUTH_TOKEN);
return cookie != null ? cookie.getValue() : null;
}
public static UserIdsPair getUserIdsPair() {
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
.map(a -> ((JwtAuthentication) a).getUserIdsPair())
.orElse(null);
}
public static String getCurrentUsername() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
return auth.getName();
}
return null;
}
}

View file

@ -790,9 +790,14 @@ JBPM использует 3 корневых категории логирова
- `ERVU_FILE_UPLOAD_MAX_FILE_SIZE` - определяет максимальный размер загружаемого файла в байтах. Указывает предел размера для каждого индивидуального файла, который может быть загружен. Если файл превышает этот размер, загрузка будет прервана, и может быть вызвано исключение.
- `ERVU_FILE_UPLOAD_MAX_REQUEST_SIZE` - устанавливает максимальный общий размер всех файлов в одном многозадачном запросе в байтах. Это ограничение на весь запрос, включающий данные и файлы. Если общий размер запроса превышает этот параметр, загрузка файлов будет остановлена.
- `ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD` - указывает размер (в байтах), при достижении которого файл будет записан во временное хранилище на диск. Это позволяет улучшить производительность, исключая непосредственную запись мелких файлов на диск, если они не превышают указанного порога. Файлы, меньшие этого значения, могут быть сохранены в памяти.
- `FILE_WEBDAV_UPLOAD_URL` - url для подключения к WebDav
- `FILE_WEBDAV_UPLOAD_USERNAME` - логин пользователя для подключения к WebDav
- `FILE_WEBDAV_UPLOAD_PASSWORD` - пароль пользователя для подключения к WebDav
- `WEBDAV_URLS` - список url для подключения к WebDav
- `WEBDAV_USERNAME` - логин пользователя для подключения к WebDav
- `WEBDAV_PASSWORD` - пароль пользователя для подключения к WebDav
- `WEBDAV_BAD_SERVERS_CACHE_EXPIRE_SECONDS` - время жизни кэша адресов недоступных серверов WebDav (секунды)
- `WEBDAV_CLEANUP_CRON` - крон по очистке WebDav
- `WEBDAV_RETRY_DELAY` - количество попыток по операциям с файлами WebDav
- `FILE_WEBDAV_LIFETIME_SECONDS` - время жизни файла в WebDav (секунды)
- `FILE_WEBDAV_EXTENSIONS` - список расширений файлов, удаляемых с WebDav
- `AV_KAFKA_BOOTSTRAP_SERVERS` - список пар хост:порт, использующихся для установки первоначального соединения с кластером Kafka
- `AV_KAFKA_SECURITY_PROTOCOL` - протокол, используемый для взаимодействия с брокерами
- `AV_KAFKA_SASL_MECHANISM` - механизм SASL, используемый для клиентских подключений

View file

@ -6,9 +6,6 @@ DB_APP_HOST=10.10.31.119
DB_APP_PORT=5432
DB_APP_NAME=ervu_lkrp_ul
FILE_WEBDAV_UPLOAD_URL=https://ervu-webdav.k8s.micord.ru
FILE_WEBDAV_UPLOAD_USERNAME=test
FILE_WEBDAV_UPLOAD_PASSWORD=test
AV_KAFKA_MESSAGE_TOPIC_NAME=file-to-upload
AV_KAFKA_BOOTSTRAP_SERVERS=http://10.10.31.11:32609
AV_KAFKA_SECURITY_PROTOCOL=SASL_PLAINTEXT
@ -52,6 +49,10 @@ ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD=0
ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * *
COOKIE_PATH=/ul
WEBDAV_URLS=https://ervu-webdav.k8s.micord.ru
WEBDAV_USERNAME=test
WEBDAV_PASSWORD=test
WEBDAV_BAD_SERVERS_CACHE_EXPIRE_SECONDS=120
WEBDAV_CLEANUP_CRON=0 0 0 * * *
WEBDAV_RETRY_DELAY=500
FILE_WEBDAV_LIFETIME_SECONDS=300

View file

@ -16,6 +16,8 @@ http {
sendfile on;
server_tokens off;
gzip on;
# text/html doesn't need to be defined there, it's compressed always
@ -79,6 +81,8 @@ http {
index index.html;
try_files $uri @index;
add_header Content-Security-Policy "frame-ancestors 'none'; default-src 'self'; script-src 'self'; style-src 'unsafe-inline' 'self' data:; font-src 'self' data:; img-src 'self' data:;";
# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|mp3|ogg|ogv|webm|htc|woff2|woff|ttf)$ {
expires 1M;

View file

@ -54,9 +54,9 @@
<property name="security.password.regex" value="^((?=(.*\d){1,})(?=.*[a-zа-яё])(?=.*[A-ZА-ЯЁ]).{8,})$"/>
<property name="fias.enable" value="false"/>
<property name="com.arjuna.ats.arjuna.allowMultipleLastResources" value="true"/>
<property name="file.webdav.upload.url" value="https://ervu-webdav.k8s.micord.ru"/>
<property name="file.webdav.upload.username" value="test"/>
<property name="file.webdav.upload.password" value="test"/>
<property name="webdav.urls" value="https://ervu-webdav.k8s.micord.ru"/>
<property name="webdav.username" value="test"/>
<property name="webdav.password" value="test"/>
<property name="av.kafka.download-request-topic" value="ervu.lkrp.download.request"/>
<property name="av.kafka.bootstrap.servers" value="localhost:9092"/>
<property name="av.kafka.security.protocol" value="SASL_PLAINTEXT"/>
@ -97,6 +97,7 @@
<property name="webdav.retry.delay" value="500"/>
<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"/>
</system-properties>
<management>
<audit-log>

View file

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Note: A "Server" is not itself a "Container", so you may not
define subcomponents such as "Valves" at this level.
Documentation at /docs/config/server.html
-->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
<!-- APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
Documentation at /docs/config/service.html
-->
<Service name="Catalina">
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
Java HTTP Connector: /docs/config/http.html
Java AJP Connector: /docs/config/ajp.html
APR (HTTP/AJP) Connector: /docs/apr.html
Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443
This connector uses the NIO implementation. The default
SSLImplementation will depend on the presence of the APR/native
library and the useOpenSSL attribute of the AprLifecycleListener.
Either JSSE or OpenSSL style configuration may be used regardless of
the SSLImplementation selected. JSSE style configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
type="RSA" />
</SSLHostConfig>
</Connector>
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
This connector uses the APR/native implementation which always uses
OpenSSL for TLS.
Either JSSE or OpenSSL style configuration may be used. OpenSSL style
configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
certificateChainFile="conf/localhost-rsa-chain.pem"
type="RSA" />
</SSLHostConfig>
</Connector>
-->
<!-- Define an AJP 1.3 Connector on port 8009 -->
<!--
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443" />
-->
<!-- An Engine represents the entry point (within Catalina) that processes
every request. The Engine implementation for Tomcat stand alone
analyzes the HTTP headers included with the request, and passes them
on to the appropriate Host (virtual host).
Documentation at /docs/config/engine.html -->
<!-- You should set jvmRoute to support load-balancing via AJP ie :
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
-->
<Engine name="Catalina" defaultHost="localhost">
<!--For clustering, please take a look at documentation at:
/docs/cluster-howto.html (simple how to)
/docs/config/cluster.html (reference documentation) -->
<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->
<!-- Use the LockOutRealm to prevent attempts to guess user passwords
via a brute-force attack -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<!-- This Realm uses the UserDatabase configured in the global JNDI
resources under the key "UserDatabase". Any edits
that are performed against this UserDatabase are immediately
available for use by the Realm. -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
errorReportValveClass="org.apache.catalina.valves.JsonErrorReportValve"
unpackWARs="true" autoDeploy="true">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
</Engine>
</Service>
</Server>

View file

@ -4,7 +4,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId>
<version>1.9.4</version>
<version>1.9.5-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.ul</groupId>

View file

@ -0,0 +1,3 @@
document.addEventListener("DOMContentLoaded", function(event) {
document.getElementById("browser-check-info").hidden = navigator.userAgent.indexOf("Chromium GOST") > -1 || navigator.userAgent.indexOf("YaBrowser") > -1;
});

View file

@ -4,6 +4,10 @@
<link rel="stylesheet" type="text/css" href="src/resources/landing/home.css">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'unsafe-inline' 'self' data:; font-src 'self' data:; img-src 'self' data:"/>
<meta name="referrer" content="strict-origin-when-cross-origin"/>
<script src="browser_check.js"></script>
</head>
<body class="ul">
@ -25,9 +29,6 @@
</div>
</div>
</div>
<script>
document.getElementById("browser-check-info").hidden = navigator.userAgent.indexOf("Chromium GOST") > -1 || navigator.userAgent.indexOf("YaBrowser") > -1;
</script>
<div class="container-inside">
<div class="list-group lk-what">
<div>
@ -84,7 +85,7 @@
<div class="section-group">
<div class="icon-pers">
Ежегодное предоставление списка граждан мужского пола, подлежащих первоначальной постановке на воинский учет в год достижения ими возраста 17 лет
<div class="muted">Срок передачи сведений: <span class="detailed">ежегодно, в срок до 1 ноября</span></div>
<div class="muted">Срок передачи сведений: <span class="detailed">ежегодно, в срок до 1 ноября</span></div>
</div>
<div class="icon-building">
Ежегодное предоставление списка сотрудников/обучающихся в организации, подлежащих воинскому учету
@ -109,10 +110,10 @@
<span class="info"></span>Если в файле будут ошибĸи, данные не будут приняты Реестром и выгрузĸу сведений придется повторить
</div>
</div>
<div class="list-group lk-docs">
<div class="list-group lk-docs">
</div>
</div>
</div>
</body>
</html>
</html>

View file

@ -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.185.0",
"@webbpm/base-package": "3.187.1",
"ag-grid-angular": "29.0.0-micord.4",
"ag-grid-community": "29.0.0-micord.4",
"angular-calendar": "0.28.28",

View file

@ -4,7 +4,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId>
<version>1.9.4</version>
<version>1.9.5-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.ul</groupId>

View file

@ -78,7 +78,7 @@
--size-text-title: 40px;
--size-text-subtitle: 32px;
--size-text-primary: 20px;
--size-text-secondary: 16px;
--size-text-secondary: 16px;
--indent-huge: 72px;
--indent-big: 52px;
@ -94,7 +94,7 @@ body {
-ms-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
display: flex;
flex-direction: column;
padding: 0;
@ -102,17 +102,17 @@ body {
}
a {
color: var(--color-link);
color: var(--color-link);
text-decoration: none;
}
a:is(:hover, :focus, :active) {
color: var(--color-link-hover);
color: var(--color-link-hover);
}
button, a.btn {
display: flex;
justify-content: center;
align-items: center;
align-items: center;
color: var(--white);
font-family: 'InterL';
font-size: var(--size-text-secondary);
@ -120,12 +120,12 @@ button, a.btn {
height: 48px;
border: 1px solid transparent;
border-radius: 4px;
background: var(--color-link);
background: var(--color-link);
}
button:is(:hover, :focus, :active),
button:is(:hover, :focus, :active),
a.btn:is(:hover, :focus, :active) {
background: var(--color-link-hover);
cursor: pointer;
cursor: pointer;
}
.btn.btn-secondary {
color: var(--color-link);
@ -142,7 +142,7 @@ a.btn:is(:hover, :focus, :active) {
display: flex;
flex-direction: row;
align-items: center;
font-family: 'InterSB';
font-family: 'InterSB';
min-height: var(--h-header);
padding: 0 var(--w-screen);
border: 0;
@ -227,7 +227,7 @@ a.btn:is(:hover, :focus, :active) {
font-family: 'GolosB';
font-size: var(--size-text-subtitle);
margin-top: var(--indent-mini);
}
}
.container-inside .block .block-description {
font-family: 'Golos';
font-size: var(--size-text-primary);
@ -307,7 +307,7 @@ a.btn:is(:hover, :focus, :active) {
}
:is(.ul, .fl) .container-inside .list-group .paragraph .icon-text {
background: url(img/svg/text-32x32.svg) no-repeat 0 0;
}
}
:is(.ul, .fl) .container-inside .list-group .list > div {
position: relative;
@ -323,7 +323,7 @@ a.btn:is(:hover, :focus, :active) {
height: 24px;
top: 0;
left: 0;
}
}
:is(.ul, .fl) .container-inside .list-group .list > div.esia::after {
background: url(img/svg/esia-24x24.svg) no-repeat 0 0;
}
@ -426,7 +426,7 @@ a.btn:is(:hover, :focus, :active) {
position: absolute;
font-family: 'InterB';
top: -50px;
left: 15px;
left: 15px;
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div:nth-child(1)::after {
content: "1";
@ -588,7 +588,7 @@ a.btn:is(:hover, :focus, :active) {
background-color: var(--bg-form);
}
:is(.fl) .container-inside .list-group.lk-msg span {
background: url(img/svg/info.svg) no-repeat 0 4px;
background: url(img/svg/info.svg) no-repeat 0 4px;
}
:is(.fl) .container-inside .list-group.lk-limits .subtitle {
margin-bottom: 0;
@ -626,7 +626,7 @@ a.btn:is(:hover, :focus, :active) {
.browser-check-content {
font-family: 'Golos';
font-size: var(--size-text-secondary);
font-size: var(--size-text-secondary);
padding: var(--indent-mini) var(--w-screen) var(--indent-mini) calc(var(--w-screen) + 38px);
background-color: var(--bg-warn);
}
@ -636,7 +636,7 @@ a.btn:is(:hover, :focus, :active) {
}
.browser-check-text::before {
position: absolute;
content: url(../img/svg/info.svg);
content: url(img/svg/info.svg);
left: 0;
top: calc((100% - 24px) / 2);
}
@ -707,7 +707,7 @@ a.btn:is(:hover, :focus, :active) {
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div::after {
top: 10px;
}
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div + div {
margin-left: 0;
margin-top: var(--indent-mini);
@ -736,5 +736,5 @@ a.btn:is(:hover, :focus, :active) {
:is(.ul, .fl) .container-inside .list-group .docs-list > div + div {
margin-left: 0;
margin-top: var(--indent-mini);
}
}
}
}

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3ZM1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12Z" fill="#C64E1B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 11C12.5523 11 13 11.4477 13 12V16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16V12C11 11.4477 11.4477 11 12 11Z" fill="#C64E1B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 8C11 7.44772 11.4477 7 12 7H12.01C12.5623 7 13.01 7.44772 13.01 8C13.01 8.55228 12.5623 9 12.01 9H12C11.4477 9 11 8.55228 11 8Z" fill="#C64E1B"/>
</svg>

After

Width:  |  Height:  |  Size: 787 B

View file

@ -34,18 +34,8 @@ export abstract class AuthGuard implements CanActivate {
if (isAccess) {
return true;
}
else if (error) {
let userErrorMessage = 'Произошла неизвестная ошибка. Обратитесь к системному администратору';
let errorCode = this.extractCode(errorDescription);
if (errorCode) {
userErrorMessage = EsiaErrorDetail.getDescription(errorCode);
}
let errorMessage = error + ', error description = ' + errorDescription;
this.messageService.error(userErrorMessage)
throw new Error(errorMessage);
}
else if (code) {
const params = new HttpParams().set('code', code);
if (code || error) {
const params = new HttpParams().set('code', code).set('error', error);
this.httpClient.get("esia/auth",
{
params: params, responseType: 'text', observe: 'response', headers: {
@ -61,8 +51,20 @@ export abstract class AuthGuard implements CanActivate {
let errorMessage = reason.error.messages != null
? reason.error.messages
: reason.error.replaceAll('\\', '');
this.messageService.error(errorMessage);
console.error(reason);
if (error) {
errorMessage = 'Произошла неизвестная ошибка. Обратитесь к системному администратору';
let errorCode = this.extractCode(errorDescription);
if (errorCode) {
errorMessage = EsiaErrorDetail.getDescription(errorCode);
}
let consoleError = error + ', error description = ' + errorDescription;
this.messageService.error(errorMessage);
console.error(consoleError);
}
else {
this.messageService.error(errorMessage);
console.error(reason);
}
});
return false;
}

View file

@ -98,6 +98,7 @@ module.exports = {
new CopyWebpackPlugin([
{from: 'index.webpack.html', to: 'index.html'},
{from: 'home.html', to: 'home.html'},
{from: 'browser_check.js', to: 'browser_check.js'},
{from: 'src/resources/img/progress.gif', to: 'src/resources/img/progress.gif'},
{from: 'src/resources/img/logo.png', to: 'src/resources/img/logo.png'},
{from: 'src/resources/app-config.json', to: 'src/resources/app-config.json'},

View file

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>resources</artifactId>
<version>3.185.0</version>
<version>3.187.1</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.185.0</webbpm-platform.version>
<webbpm-platform.version>3.187.1</webbpm-platform.version>
<h2.version>1.4.200</h2.version>
<build.timestamp>1107112530</build.timestamp>
<build.timestamp>0113064203</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.185.0</revision>
<revision>3.187.1</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.185.0</version>
<version>3.187.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>backend</artifactId>
<version>3.185.0</version>
<version>3.187.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>frontend</artifactId>
<version>3.185.0</version>
<version>3.187.1</version>
<scope>compile</scope>
</dependency>
</dependencies>

View file

@ -60,7 +60,7 @@
<ul>
<li>&#x41e;&#x431;&#x440;&#x430;&#x437;&#x435;&#x446; &#x432;&#x43d;&#x435;&#x448;&#x43d;&#x435;&#x439; &#x441;&#x441;&#x44b;&#x43b;&#x43a;&#x438;: <code>https://www.wildberries.ru/catalog/${sku}/detail.aspx</code></li>
<li>&#x41e;&#x431;&#x440;&#x430;&#x437;&#x435;&#x446; &#x432;&#x43d;&#x443;&#x442;&#x440;&#x435;&#x43d;&#x43d;&#x435;&#x439; &#x441;&#x441;&#x44b;&#x43b;&#x43a;&#x438;: <code>products/ru.cg.webbpm.packages.base:resources:jar:3.185.0</code></li>
<li>&#x41e;&#x431;&#x440;&#x430;&#x437;&#x435;&#x446; &#x432;&#x43d;&#x443;&#x442;&#x440;&#x435;&#x43d;&#x43d;&#x435;&#x439; &#x441;&#x441;&#x44b;&#x43b;&#x43a;&#x438;: <code>products/ru.cg.webbpm.packages.base:resources:jar:3.187.1</code></li>
</ul>
</li>
<li>

View file

@ -4,17 +4,17 @@
<description>Base webbpm package</description>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>resources</artifactId>
<version>3.185.0</version>
<studioVersion>3.185.0</studioVersion>
<version>3.187.1</version>
<studioVersion>3.187.1</studioVersion>
<backendModule>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>backend</artifactId>
<version>3.185.0</version>
<version>3.187.1</version>
</backendModule>
<frontendModule>
<packageName>@webbpm/base-package</packageName>
<version>3.185.0</version>
<version>3.187.1</version>
</frontendModule>
</packageInfo>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_отмены.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_очистки_фильтра.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_удаления.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопкаагрузки.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_вызова_ошибки.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<documentation>component/buttons/Кнопка_выполнения_бизнес-процесса.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_выполнения_SQL.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_для_фильтрации.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопкаавигации.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_сохранения.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_выбора.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_подписи.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопкаапуска_бизнес-процесса.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/reporting/Кнопка_печати_из_графа_сущности.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/reporting/Кнопка_печати_отчета_из_формы.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Сворачиваемая_панель.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Диалог.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Контейнер_с_кнопками.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Группа_полей.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Набор_фильтров.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Форма.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Горизонтальный_контейнер.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Контейнер_вкладок.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Вкладка.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Вертикальный_контейнер.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Окно.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/editable-grids/EditableGrid.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/autocomplete</localization>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/check-box</localization>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/combo-box</localization>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/date-time-picker</localization>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/money-field</localization>
<internal>true</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/number-field</localization>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/one-to-many</localization>
<internal>true</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/one-to-many</localization>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -6,11 +6,11 @@
<localization>META-INF/components/localization/editable-grids/read-only</localization>
<internal>true</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -5,11 +5,11 @@
<category>editable-grids</category>
<internal>true</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>Статичный_выпадающий_список_колонки_таблицы.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/text-area</localization>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/text-field</localization>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/time-picker</localization>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/ФИАС.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>
@ -115,7 +115,7 @@
<value>
<expanded>true</expanded>
<implRef type="JAVA">
<className>FiasAddressServiceImpl</className>
<className>GarAddressServiceImpl</className>
<packageName>service.field</packageName>
</implRef>
<complex>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Поле_ввода_с_подбором_значения.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Флаг.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Выпадающий_список.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Дата.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/EditableOneToMany.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Файл.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Файл.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/ManyToMany.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/ManyToManyField.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Денежное_поле.html</documentation>
<internal>true</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Числовое_поле.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/OneToMany.html</documentation>
<internal>true</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/OneToMany.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Переключатель.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/SignVerification.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Статичный_выпадающий_список.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Статичный_переключатель.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Текст.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Многострочное_поле.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Текстовое_поле.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Время.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/TreeField.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.185.0</studioVersion>
<studioVersion>3.187.1</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.185.0</value>
<value>3.187.1</value>
</entry>
</packageVersions>
</versions>

Some files were not shown because too many files have changed in this diff Show more