Merge branch 'hotfix/1.9.1' into feateure/SUPPORT-8653_fixes

# Conflicts:
#	backend/pom.xml
#	frontend/package.json
#	packages/ru.cg.webbpm.packages.base.resources/.flattened-pom.xml
#	packages/ru.cg.webbpm.packages.base.resources/META-INF/components/docs/component/grids/NavigateOnGridCellOrRow.html
#	packages/ru.cg.webbpm.packages.base.resources/META-INF/package-descriptor.xml
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/Button.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/CancelButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/ClearFilterButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/DeleteButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/DownloadButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/ErrorButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/ExecProcessButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/ExecuteSqlButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/FilterButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/NavigationButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/SaveButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/SelectButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/SignButtonV2.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/StartProcessButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/reporting/EntityGraphReportingButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/buttons/reporting/FormReportingButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/CollapsiblePanel.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/Dialog.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/DropDown.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/FieldSet.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/FilterGroup.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/Form.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/HBox.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/TabContainer.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/TabItem.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/VBox.component
#	packages/ru.cg.webbpm.packages.base.resources/component/containers/Window.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/EditableGrid.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/AutocompleteGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/CheckBoxGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/ComboBoxGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/DateTimePickerGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/MoneyFieldGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/NumberFieldGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/OneToManyGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/OneToManyGridColumnV2.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/ReadonlyClientGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/ReadonlyServerGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/StaticComboBoxGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/TextAreaGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/TextFieldGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/editable-grids/columns/TimePickerGridColumn.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/Address.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/Autocomplete.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/CheckBox.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/ComboBox.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/DateTimePicker.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/EditableOneToMany.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/FilePreview.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/FileUpload.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/ManyToMany.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/ManyToManyField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/MoneyField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/NumberField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/OneToMany.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/OneToManyV2.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/RadioButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/SignVerification.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/StaticComboBox.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/StaticRadioButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/Text.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/TextArea.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/TextField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/TimePicker.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/TreeField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/editableonetomany/EditableGrid.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/editableonetomany/Form.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/fileupload/FileUploadField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterAddress.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterAutocomplete.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterBoolean.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterCheckBox.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterComboBox.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterDateTimePicker.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterNumberField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterRadioButton.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterStaticComboBox.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterTextArea.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/filters/FilterTextField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/treefield/LinkField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/fields/treefield/MultiValueField.component
#	packages/ru.cg.webbpm.packages.base.resources/component/grids/Grid.component
#	packages/ru.cg.webbpm.packages.base.resources/component/grids/GridV2.component
#	packages/ru.cg.webbpm.packages.base.resources/component/grids/GridV2Column.component
#	packages/ru.cg.webbpm.packages.base.resources/component/grids/GridV2Group.component
#	packages/ru.cg.webbpm.packages.base.resources/component/grids/PagingGrid.component
#	packages/ru.cg.webbpm.packages.base.resources/component/grids/TreeGrid.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/ActionController.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/Calendar.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/Chart.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/ChartV2.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/EventCalendar.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/GanttChart.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/HyperLink.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/IFrame.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/InnerHtml.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/MenuGroup.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/MenuItem.component
#	packages/ru.cg.webbpm.packages.base.resources/component/uncategorized/ValidationController.component
#	packages/ru.cg.webbpm.packages.base.resources/converters/converters.jar
#	packages/ru.cg.webbpm.packages.base.resources/metadata/java.metadata
#	packages/ru.cg.webbpm.packages.base.resources/process-instance/ProcessInstance.component
#	packages/ru.cg.webbpm.packages.base.resources/process-instance/ProcessInstanceDiagram.component
#	packages/ru.cg.webbpm.packages.base.resources/process-instance/ProcessInstanceList.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/Groups.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/GroupsCreate.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/GroupsEdit.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/OrganizationEdit.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/Organizations.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/Roles.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/RolesEdit.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/SecurityFeatures.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/StaticRouteNavigationButton.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/StaticRouteSelectButton.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/UserForm.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/Users.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/UsersCreate.component
#	packages/ru.cg.webbpm.packages.base.resources/user-management/UsersEdit.component
#	pom.xml
This commit is contained in:
kochetkov 2024-11-02 21:09:31 +03:00
commit f8aef7dba5
116 changed files with 2841 additions and 1047 deletions

View file

@ -222,3 +222,6 @@ mvn webbpm:update-package -DexecuteNpmInstall=false -Dpath=resources-<your-versi
в директорию: {your-project}\frontend\node_modules\@webbpm\base-package\
```
4. Запретите выполнение npm install при запуске студии. Для этого добавьте параметр `-DexecuteNpmInstall=false` в настройках Run/Debug Configurations студии
Утилита для вычисления ESIA_CLIENT_CERT_HASH - https://esia.gosuslugi.ru/public/calc_cert_hash_unix.zip

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.9.1-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.ul</groupId>
<artifactId>backend</artifactId>
@ -180,7 +180,7 @@
</dependency>
</dependencies>
<build>
<finalName>${parent.artifactId}</finalName>
<finalName>${project.parent.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>

View file

@ -1,4 +1,5 @@
import java.time.Duration;
import java.util.List;
import javax.sql.DataSource;
import liquibase.integration.spring.SpringLiquibase;
@ -14,10 +15,13 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Root application context
@ -41,12 +45,13 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
}, excludeFilters = {
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "security.WebSecurityConfig"),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "ru.cg.webbpm.modules.database.impl.DatabaseConfiguration"),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "service.email.EmailConfig"),
})
@EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableWebMvc
@EnableScheduling
@EnableRetry
public class AppConfig {
public class AppConfig implements WebMvcConfigurer {
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
@ -77,4 +82,9 @@ public class AppConfig {
liquibase.setChangeLog("classpath:config/changelog-master.xml");
return liquibase;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new ResourceHttpMessageConverter());
}
}

View file

@ -0,0 +1,5 @@
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.web.WebApplicationInitializer;
public class SecurityInit extends AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {
}

View file

@ -1,24 +1,20 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import org.springframework.web.util.IntrospectorCleanupListener;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import org.springframework.web.util.IntrospectorCleanupListener;
/**
* This initializer creates root context and registers dispatcher servlet
* Spring scans for initializers automatically
*/
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Value("${ervu.fileupload.max_file_size}")
private int maxFileSize;
@Value("${ervu.fileupload.max_request_size}")
private int maxRequestSize;
@Value("${ervu.fileupload.file_size_threshold}")
private int fileSizeThreshold;
private static final Logger logger = LoggerFactory.getLogger(WebAppInitializer.class);
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
@ -41,11 +37,37 @@ public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServlet
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// read from env or assign default values
int maxFileSize = parseOrDefault("ERVU_FILE_UPLOAD_MAX_FILE_SIZE", 5242880);
int maxRequestSize = parseOrDefault("ERVU_FILE_UPLOAD_MAX_REQUEST_SIZE", 6291456);
int fileSizeThreshold = parseOrDefault("ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD", 0);
MultipartConfigElement multipartConfigElement = new MultipartConfigElement(
"/tmp",
maxFileSize,
maxRequestSize,
fileSizeThreshold);
registration.setMultipartConfig(multipartConfigElement);
logger.info("Max file upload size is set to: " + multipartConfigElement.getMaxFileSize());
logger.info("Max file upload request size is set to: " + multipartConfigElement.getMaxRequestSize());
logger.info("File size threshold is set to: " + multipartConfigElement.getFileSizeThreshold());
}
private int parseOrDefault(String envVar, int defaultVal) {
String envVarValue = System.getenv(envVar);
if (envVar == null) {
logger.info("Environment variable {} is null, using default value: {}", envVar, defaultVal);
return defaultVal;
}
try {
return Integer.parseInt(envVarValue);
} catch (NumberFormatException e) {
logger.info("Environment variable {} is not an integer, using default value: {}", envVar, defaultVal);
return defaultVal;
}
}
}

View file

@ -0,0 +1,92 @@
package ervu;
import java.util.HashMap;
import java.util.Map;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.config.SaslConfigs;
import org.apache.kafka.common.serialization.StringDeserializer;
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.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
/**
* @author Alexandr Shalaginov
*/
@Configuration
public class AvKafkaConfig {
@Value("${av.kafka.bootstrap.servers}")
private String kafkaUrl;
@Value("${av.kafka.security.protocol}")
private String securityProtocol;
@Value("${av.kafka.login.module:org.apache.kafka.common.security.scram.ScramLoginModule}")
private String loginModule;
@Value("${av.kafka.username}")
private String username;
@Value("${av.kafka.password}")
private String password;
@Value("${av.kafka.sasl.mechanism}")
private String saslMechanism;
@Bean
public ProducerFactory<String, String> avProducerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
@Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, this.kafkaUrl);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
props.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\""
+ username + "\" password=\"" + password + "\";");
props.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
return props;
}
@Bean
public ConsumerFactory<String, String> avConsumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
@Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, this.kafkaUrl);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
props.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\""
+ username + "\" password=\"" + password + "\";");
props.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
return props;
}
@Bean("avContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(avConsumerFactory());
return factory;
}
@Bean("avTemplate")
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(avProducerFactory());
}
}

View file

@ -1,60 +0,0 @@
package ervu;
import java.util.HashMap;
import java.util.Map;
import org.apache.kafka.clients.CommonClientConfigs;
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.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
/**
* @author Alexandr Shalaginov
*/
@Configuration
public class KafkaProducerConfig {
@Value("${av.kafka.send.url}")
private String kafkaUrl;
@Value("${av.kafka.send.security.protocol}")
private String securityProtocol;
@Value("${av.kafka.send.login.module:org.apache.kafka.common.security.scram.ScramLoginModule}")
private String loginModule;
@Value("${av.kafka.send.username}")
private String username;
@Value("${av.kafka.send.password}")
private String password;
@Value("${av.kafka.sasl.mechanism}")
private String saslMechanism;
@Bean("av-factory")
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
@Bean("av-configs")
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, this.kafkaUrl);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
props.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\""
+ username + "\" password=\"" + password + "\";");
props.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
return props;
}
@Bean("av-template")
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}

View file

@ -1,11 +1,17 @@
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;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
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;
/**
@ -21,25 +27,29 @@ public class EmployeeInfoFileUploadController {
@RequestMapping(value = "/employee/document", method = RequestMethod.POST)
public ResponseEntity<?> saveEmployeeInformationFile(@RequestParam("file") MultipartFile multipartFile,
@RequestHeader("X-Employee-Info-File-Form-Type") String formType, HttpServletRequest request) {
@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("access_token")) {
accessToken = cookie.getValue();
}
else if (cookie.getName().equals("auth_token")) {
if (cookie.getName().equals("auth_token")) {
authToken = cookie.getValue();
}
}
}
if (accessToken != null && this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType, accessToken, authToken)) {
return ResponseEntity.ok("File successfully uploaded.");
}
else {
return ResponseEntity.internalServerError().body("An error occurred while uploading file.");
if (authToken != null) {
String offset = ZonedDateTime.now(TimeZone.getTimeZone(clientTimeZone).toZoneId())
.getOffset().getId();
if (this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType,
authToken, offset)) {
return ResponseEntity.ok("File successfully uploaded.");
}
}
return ResponseEntity.internalServerError().body("An error occurred while uploading file.");
}
}

View file

@ -10,7 +10,7 @@ import ervu.model.okopf.OkopfModel;
* @author Artyom Hackimullin
*/
public interface OkopfDao {
void save(List<OkopfModel> recordModels, int version);
void save(List<OkopfModel> recordModels);
String fetchTitleByLeg(String leg);
}

View file

@ -20,14 +20,14 @@ public class OkopfDaoImpl implements OkopfDao {
private DSLContext dsl;
@Override
public void save(List<OkopfModel> recordModels, int version) {
public void save(List<OkopfModel> recordModels) {
var queries = recordModels.stream().map(record ->
dsl.insertInto(OKOPF_RECORDS, OKOPF_RECORDS.OKOPF_RECORDS_ID, OKOPF_RECORDS.NAME, OKOPF_RECORDS.VERSION)
.values(record.getCode(), record.getName(), version)
.values(record.getCode(), record.getName(), record.getVersion())
.onConflict(OKOPF_RECORDS.OKOPF_RECORDS_ID)
.doUpdate()
.set(OKOPF_RECORDS.NAME, record.getName())
.set(OKOPF_RECORDS.VERSION, version)
.set(OKOPF_RECORDS.VERSION, record.getVersion())
.where(OKOPF_RECORDS.OKOPF_RECORDS_ID.eq(record.getCode()))
).toList();

View file

@ -0,0 +1,20 @@
package ervu.enums;
/**
* @author gulnaz
*/
public enum FileStatusCode {
FILE_INFECTED("02"),
FILE_CLEAN("03"),
FILE_NOT_CHECKED("11");
private final String code;
FileStatusCode(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}

View file

@ -0,0 +1,7 @@
package ervu.model.fileupload;
/**
* @author r.latypov
*/
public record DownloadResponse(OrgInfo orgInfo, FileInfo fileInfo) {
}

View file

@ -6,14 +6,17 @@ import java.util.Objects;
* @author Alexandr Shalaginov
*/
public class FileInfo {
private final String fileId;
private final String fileUrl;
private final String fileName;
private final String filePatternCode;
private final String filePatternName;
private final String departureDateTime;
private final String timeZone;
private final FileStatus fileStatus;
private String fileId;
private String fileUrl;
private String fileName;
private String filePatternCode;
private String filePatternName;
private String departureDateTime;
private String timeZone;
private FileStatus fileStatus;
public FileInfo() {
}
public FileInfo(String fileId, String fileUrl, String fileName, String filePatternCode,
String filePatternName, String departureDateTime, String timeZone, FileStatus fileStatus) {

View file

@ -8,9 +8,12 @@ import ru.micord.ervu.journal.SenderInfo;
* @author Alexandr Shalaginov
*/
public class OrgInfo {
private final String orgName;
private final String orgId;
private final SenderInfo senderInfo;
private String orgName;
private String orgId;
private SenderInfo senderInfo;
public OrgInfo() {
}
public OrgInfo(String orgName, String orgId, SenderInfo senderInfo) {
this.orgName = orgName;
@ -26,7 +29,7 @@ public class OrgInfo {
return orgId;
}
public SenderInfo getPrnOid() {
public SenderInfo getSenderInfo() {
return senderInfo;
}

View file

@ -12,9 +12,12 @@ public class OkopfModel implements Serializable {
private String name;
public OkopfModel(String code, String name) {
private int version;
public OkopfModel(String code, String name, int version) {
this.code = code;
this.name = name;
this.version = version;
}
public String getCode() {
@ -33,11 +36,20 @@ public class OkopfModel implements Serializable {
this.name = name;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
@Override
public String toString() {
return "OkopfRecordModel{" +
return "OkopfModel{" +
"code='" + code + '\'' +
", name='" + name + '\'' +
", version=" + version +
'}';
}
}

View file

@ -1,25 +1,30 @@
package ervu.service.fileupload;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.UUID;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ervu.client.fileupload.FileUploadWebDavClient;
import ervu.model.fileupload.DownloadResponse;
import ervu.model.fileupload.EmployeeInfoFileFormType;
import ervu.model.fileupload.EmployeeInfoKafkaMessage;
import ervu.model.fileupload.FileInfo;
import ervu.model.fileupload.FileStatus;
import org.apache.kafka.clients.producer.ProducerRecord;
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.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.TokensStore;
import ru.micord.ervu.security.esia.model.EmployeeModel;
import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.esia.service.UlDataService;
@ -27,13 +32,16 @@ import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import ru.micord.ervu.service.InteractionService;
import static ervu.enums.FileStatusCode.*;
import static ru.micord.ervu.util.StringUtils.convertToFio;
/**
* @author Alexandr Shalaginov
*/
@Service
public class EmployeeInfoFileUploadService {
private static final Logger logger = LoggerFactory.getLogger(EmployeeInfoFileUploadService.class);
private static final String FORMAT = "dd.MM.yyyy HH:mm";
private static final String FORMAT = "dd.MM.yyyy HH:mm:ss";
private final FileUploadWebDavClient fileWebDavUploadClient;
private final EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService;
@ -42,7 +50,7 @@ public class EmployeeInfoFileUploadService {
private final UlDataService ulDataService;
private final JwtTokenService jwtTokenService;
@Value("${av.kafka.send.message.topic.name}")
@Value("${av.kafka.message.topic.name}")
private String kafkaTopicName;
@Value("${file.webdav.upload.url:http://localhost:5757}")
private String url;
@ -54,7 +62,8 @@ public class EmployeeInfoFileUploadService {
public EmployeeInfoFileUploadService(
FileUploadWebDavClient fileWebDavUploadClient,
EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService,
@Qualifier("av-template") KafkaTemplate<String, String> kafkaTemplate, InteractionService interactionService,
@Qualifier("avTemplate") KafkaTemplate<String, String> kafkaTemplate,
InteractionService interactionService,
UlDataService ulDataService, JwtTokenService jwtTokenService) {
this.fileWebDavUploadClient = fileWebDavUploadClient;
this.employeeInfoKafkaMessageService = employeeInfoKafkaMessageService;
@ -64,12 +73,9 @@ public class EmployeeInfoFileUploadService {
this.jwtTokenService = jwtTokenService;
}
public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType,
String accessToken, String authToken) {
public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType, String authToken, String offset) {
String fileUploadUrl = this.url + "/" + getNewFilename(multipartFile.getOriginalFilename());
LocalDateTime now = LocalDateTime.now();
String departureDateTime = now.format(DateTimeFormatter.ofPattern(FORMAT));;
String timeZone = getTimeZone();
if (this.fileWebDavUploadClient.webDavUploadFile(fileUploadUrl, username, password, multipartFile)) {
FileStatus fileStatus = new FileStatus();
@ -79,10 +85,14 @@ public class EmployeeInfoFileUploadService {
String fileId = UUID.randomUUID().toString();
String fileName = multipartFile.getOriginalFilename();
EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(formType);
EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken);
PersonModel personModel = employeeModel.getPerson();
Token token = jwtTokenService.getToken(authToken);
String[] ids = token.getUserAccountId().split(":");
String userId = ids[0];
String ervuId = ids[1];
String accessToken = TokensStore.getAccessToken(userId);
EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken);
PersonModel personModel = employeeModel.getPerson();
String departureDateTime = now.format(DateTimeFormatter.ofPattern(FORMAT));
String jsonMessage = getJsonKafkaMessage(
employeeInfoKafkaMessageService.getKafkaMessage(
fileId,
@ -91,16 +101,17 @@ public class EmployeeInfoFileUploadService {
employeeInfoFileFormType,
departureDateTime,
accessToken,
timeZone,
offset,
fileStatus,
ids[1],
ids[0],
ervuId,
userId,
personModel
)
);
interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, employeeInfoFileFormType.getFilePatternName(), Timestamp.valueOf(now),
personModel.getLastName() + " " + personModel.getFirstName().charAt(0) + ". " + personModel.getMiddleName().charAt(0) + ".", (int) multipartFile.getSize(),
ids[1]);
interactionService.setStatus(fileId, fileStatus.getStatus(), fileName,
employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now),
convertToFio(personModel.getFirstName(), personModel.getMiddleName(), personModel.getLastName()),
(int) multipartFile.getSize(), ervuId);
return sendMessage(jsonMessage);
}
else {
@ -111,6 +122,8 @@ public class EmployeeInfoFileUploadService {
private boolean sendMessage(String message) {
ProducerRecord<String, String> record = new ProducerRecord<>(this.kafkaTopicName, message);
record.headers().add("messageId", UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
try {
this.kafkaTemplate.send(record).get();
logger.debug("Success send record: {}", record);
@ -144,7 +157,27 @@ public class EmployeeInfoFileUploadService {
}
}
private String getTimeZone() {
return ZonedDateTime.now().getOffset().toString();
@KafkaListener(id = "${av.kafka.group.id}", topics = "${av.kafka.download.response}",
containerFactory = "avContainerFactory")
public void listenKafka(String kafkaMessage) {
ObjectMapper mapper = new ObjectMapper();
try {
DownloadResponse downloadResponse = mapper.readValue(kafkaMessage, DownloadResponse.class);
FileInfo fileInfo = downloadResponse.fileInfo();
String statusCode = fileInfo.getFileStatus().getCode();
if (Arrays.asList(FILE_INFECTED.getCode(), FILE_CLEAN.getCode()).contains(statusCode)) {
interactionService.delete(fileInfo.getFileId(), downloadResponse.orgInfo().getOrgId());
}
else if (statusCode.equals(FILE_NOT_CHECKED.getCode())) {
interactionService.updateStatus(fileInfo.getFileId(), fileInfo.getFileStatus().getStatus(),
downloadResponse.orgInfo().getOrgId()
);
}
}
catch (JsonProcessingException e) {
throw new RuntimeException(String.format("Fail get json from: %s", kafkaMessage), e);
}
}
}

View file

@ -25,7 +25,7 @@ public class EmployeeInfoKafkaMessageService {
public EmployeeInfoKafkaMessage getKafkaMessage(String fileId, String fileUrl, String fileName,
EmployeeInfoFileFormType formType, String departureDateTime, String accessToken,
String timeZone, FileStatus fileStatus, String ervuId, String prnOid, PersonModel personModel) {
String offset, FileStatus fileStatus, String ervuId, String prnOid, PersonModel personModel) {
return new EmployeeInfoKafkaMessage(
getOrgInfo(accessToken, ervuId, prnOid, personModel),
getFileInfo(
@ -34,14 +34,14 @@ public class EmployeeInfoKafkaMessageService {
fileName,
formType,
departureDateTime,
timeZone,
offset,
fileStatus
)
);
}
private FileInfo getFileInfo(String fileId, String fileUrl, String fileName,
EmployeeInfoFileFormType formType, String departureDateTime, String timeZone,
EmployeeInfoFileFormType formType, String departureDateTime, String offset,
FileStatus fileStatus) {
return new FileInfo(
fileId,
@ -50,7 +50,7 @@ public class EmployeeInfoKafkaMessageService {
formType.getFilePatternCode(),
formType.getFilePatternName(),
departureDateTime,
timeZone,
offset,
fileStatus
);
}

View file

@ -17,11 +17,15 @@ import net.javacrumbs.shedlock.core.SchedulerLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.scheduling.config.ScheduledTaskRegistrar.CRON_DISABLED;
/**
* @author Artyom Hackimullin
*/
@ -37,38 +41,45 @@ public class EsnsiOkopfSchedulerServiceImpl implements EsnsiOkopfSchedulerServic
private OkopfDao okopfDao;
@Autowired
private ObjectMapper mapper;
@Value("${esnsi.okopf.cron.load:0 0 */1 * * *}")
private String cronLoad;
@PostConstruct
@Transactional
public void init() {
load();
if (!cronLoad.equals(CRON_DISABLED)) {
logger.info("Synchronization with OKOPF enabled");
load();
}
else {
logger.info("Synchronization with OKOPF disabled");
}
}
@Scheduled(cron = "${esnsi.okopf.cron:0 0 */1 * * *}")
@Scheduled(cron = "${esnsi.okopf.cron.load:0 0 */1 * * *}")
@SchedulerLock(name = "loadOkopf")
@Transactional
public void load() {
String data = esnsiOkopfClient.getJsonOkopFormData();
try {
logger.info("Start okopf scheduller. Load okopf file from esnsi");
logger.info("Loading okopf file");
String data = esnsiOkopfClient.getJsonOkopFormData();
OkopfOrgModel orgModel = mapper.readValue(data, OkopfOrgModel.class);
List<OkopfModel> okopfRecords = mapToOkopfRecords(orgModel.getData());
int currentVersion = mapper.readTree(data).findValue("version").asInt();
logger.info("Saving okopf data in database.");
okopfDao.save(okopfRecords, currentVersion);
List<OkopfModel> okopfRecords = mapToOkopfRecords(orgModel.getData(), currentVersion);
okopfDao.save(okopfRecords);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
private List<OkopfModel> mapToOkopfRecords(OkopfDataModel dataModel) {
private List<OkopfModel> mapToOkopfRecords(OkopfDataModel dataModel, int version) {
return Arrays.stream(dataModel.getDetails())
.flatMap(detail -> {
OkopfAttributeValueModel[] attributeValues = detail.getAttributeValues();
String key = attributeValues[0].getValue();
String value = attributeValues[1].getValue();
return Stream.of(new OkopfModel(key, value));
return Stream.of(new OkopfModel(key, value, version));
})
.toList();
}

View file

@ -1,8 +1,6 @@
package ru.micord.ervu.journal.mapper;
import java.sql.Timestamp;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.records.InteractionLogRecord;
import ru.micord.ervu.journal.JournalDto;
@ -16,9 +14,7 @@ public class JournalDtoMapper {
public static JournalDto mapToJournalDto(JournalFileInfo journalFileInfo) {
SenderInfo senderInfo = journalFileInfo.getSenderInfo();
return new JournalDto()
.setDepartureDateTime(Timestamp.from(ZonedDateTime.of(journalFileInfo.getDepartureDateTime(),
ZoneOffset.of(journalFileInfo.getTimeZone())
).toInstant()).toString())
.setDepartureDateTime(Timestamp.valueOf(journalFileInfo.getDepartureDateTime()).toString())
.setFileName(journalFileInfo.getFileName())
.setFilePatternCode(journalFileInfo.getFilePatternCode())
.setSenderFio(convertToFio(senderInfo.getFirstName(), senderInfo.getMiddleName(),
@ -33,7 +29,7 @@ public class JournalDtoMapper {
return new JournalDto()
.setDepartureDateTime(record.getSentDate().toString())
.setFileName(record.getFileName())
.setFilePatternCode(Integer.valueOf(record.getForm().replace("", "")))
.setFilePatternCode(Integer.valueOf(record.getForm()))
.setSenderFio(record.getSender())
.setStatus(record.getStatus())
.setFilesSentCount(record.getRecordsSent())

View file

@ -12,13 +12,19 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.*;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import org.springframework.kafka.requestreply.CorrelationKey;
import org.springframework.kafka.requestreply.ReplyingKafkaTemplate;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Configuration
@EnableKafka
@ -34,19 +40,20 @@ public class ReplyingKafkaConfig {
private String groupId;
@Value("${ervu.kafka.reply.timeout:30}")
private long replyTimeout;
@Value("${ervu.kafka.send.security.protocol}")
@Value("${ervu.kafka.excerpt.reply.topic}")
private String excerptReplyTopic;
@Value("${ervu.kafka.security.protocol}")
private String securityProtocol;
@Value("${ervu.kafka.send.login.module:org.apache.kafka.common.security.scram.ScramLoginModule}")
@Value("${ervu.kafka.login.module:org.apache.kafka.common.security.scram.ScramLoginModule}")
private String loginModule;
@Value("${ervu.kafka.send.username}")
@Value("${ervu.kafka.username}")
private String username;
@Value("${ervu.kafka.send.password}")
@Value("${ervu.kafka.password}")
private String password;
@Value("${ervu.kafka.sasl.mechanism}")
private String saslMechanism;
@Bean("ervu")
@Bean("ervuProducerFactory")
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
@ -59,11 +66,6 @@ public class ReplyingKafkaConfig {
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
@Bean
public ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> configProps = new HashMap<>();
@ -78,55 +80,32 @@ public class ReplyingKafkaConfig {
return new DefaultKafkaConsumerFactory<>(configProps);
}
@Bean
@Bean("ervuContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
ConcurrentKafkaListenerContainerFactory<String, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
@Bean
@Qualifier("org")
public ConcurrentMessageListenerContainer<String, String> replyContainer(
ConcurrentKafkaListenerContainerFactory<String, String> factory) {
ConcurrentMessageListenerContainer<String, String> container = factory.createContainer(
orgReplyTopic);
@Qualifier("ervuContainerFactory") ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory) {
ConcurrentMessageListenerContainer<String, String> container =
kafkaListenerContainerFactory.createContainer(orgReplyTopic, excerptReplyTopic, journalReplyTopic);
container.getContainerProperties().setGroupId(groupId);
return container;
}
@Bean
@Qualifier("journal")
public ConcurrentMessageListenerContainer<String, String> journalReplyContainer(
ConcurrentKafkaListenerContainerFactory<String, String> factory) {
ConcurrentMessageListenerContainer<String, String> container = factory.createContainer(
journalReplyTopic);
container.getContainerProperties().setGroupId(groupId);
return container;
}
@Bean
@Qualifier("org")
public ReplyingKafkaTemplate<String, String, String> orgReplyingKafkaTemplate(
@Qualifier("ervu") ProducerFactory<String, String> pf,
@Qualifier("org") ConcurrentMessageListenerContainer<String, String> container) {
return initReplyingKafkaTemplate(pf, container);
}
@Bean
@Qualifier("journal")
public ReplyingKafkaTemplate<String, String, String> journalReplyingKafkaTemplate(
@Qualifier("ervu") ProducerFactory<String, String> pf,
@Qualifier("journal") ConcurrentMessageListenerContainer<String, String> container) {
return initReplyingKafkaTemplate(pf, container);
}
private ReplyingKafkaTemplate<String, String, String> initReplyingKafkaTemplate(
ProducerFactory<String, String> pf,
ConcurrentMessageListenerContainer<String, String> container) {
public ReplyingKafkaTemplate<String, String, String> replyingKafkaTemplate(
@Qualifier("ervuProducerFactory") ProducerFactory<String, String> producerFactory,
ConcurrentMessageListenerContainer<String, String> replyContainer) {
ReplyingKafkaTemplate<String, String, String> replyingKafkaTemplate =
new ReplyingKafkaTemplate<>(pf, container);
replyingKafkaTemplate.setCorrelationHeaderName("messageID");
new ReplyingKafkaTemplate<>(producerFactory, replyContainer);
replyingKafkaTemplate.setCorrelationHeaderName("messageId");
replyingKafkaTemplate.setCorrelationIdStrategy(record ->
new CorrelationKey(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)));
replyingKafkaTemplate.setDefaultReplyTimeout(Duration.ofSeconds(replyTimeout));
return replyingKafkaTemplate;
}

View file

@ -0,0 +1,78 @@
package ru.micord.ervu.kafka.controller;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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.s3.S3Service;
import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
/**
* @author Eduard Tihomirov
*/
@RestController
public class ErvuKafkaController {
@Autowired
private ReplyingKafkaService replyingKafkaService;
@Autowired
private S3Service s3Service;
@Autowired
private JwtTokenService jwtTokenService;
@Value("${ervu.kafka.excerpt.reply.topic}")
private String requestReplyTopic;
@Value("${ervu.kafka.excerpt.request.topic}")
private String requestTopic;
@Autowired
private ObjectMapper objectMapper;
@RequestMapping(value = "/kafka/excerpt")
public ResponseEntity<Resource> getExcerptFile(HttpServletRequest request) {
try {
String authToken = getAuthToken(request);
Token token = jwtTokenService.getToken(authToken);
String[] split = token.getUserAccountId().split(":");
String prnOid = split[0];
String ervuId = split[1];
Data data = new Data();
data.setOrgId_ERVU(ervuId);
data.setPrnOid(prnOid);
String kafkaResponse = replyingKafkaService.sendMessageAndGetReply(requestTopic, requestReplyTopic,
objectMapper.writeValueAsString(data)
);
ExcerptResponse excerptResponse = objectMapper.readValue(kafkaResponse, ExcerptResponse.class);
return s3Service.getFile(excerptResponse.getFileUrl());
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private String getAuthToken(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("auth_token")) {
return cookie.getValue();
}
}
}
return null;
}
}

View file

@ -13,7 +13,7 @@ public class ErvuOrgResponse implements Serializable {
private static final long serialVersionUID = 1L;
private boolean success;
private String message;
private Data[] data;
private Data data;
public boolean getSuccess() {
return success;
@ -31,11 +31,11 @@ public class ErvuOrgResponse implements Serializable {
this.message = message;
}
public Data[] getData() {
public Data getData() {
return data;
}
public void setData(Data[] data) {
public void setData(Data data) {
this.data = data;
}
}

View file

@ -0,0 +1,32 @@
package ru.micord.ervu.kafka.model;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* @author Eduard Tihomirov
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ExcerptResponse implements Serializable {
private static final long serialVersionUID = 1L;
private String orgId;
private String fileUrl;
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
public String getFileUrl() {
return fileUrl;
}
public void setFileUrl(String fileUrl) {
this.fileUrl = fileUrl;
}
}

View file

@ -9,23 +9,28 @@ import org.apache.kafka.common.header.internals.RecordHeader;
import org.springframework.kafka.requestreply.ReplyingKafkaTemplate;
import org.springframework.kafka.requestreply.RequestReplyFuture;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.stereotype.Service;
import ru.micord.ervu.kafka.service.ReplyingKafkaService;
/**
* @author Eduard Tihomirov
*/
public abstract class BaseReplyingKafkaServiceImpl implements ReplyingKafkaService {
@Service
public class BaseReplyingKafkaServiceImpl implements ReplyingKafkaService {
protected abstract ReplyingKafkaTemplate<String, String, String> getReplyingKafkaTemplate();
private final ReplyingKafkaTemplate<String, String, String> replyingKafkaTemplate;
public BaseReplyingKafkaServiceImpl(
ReplyingKafkaTemplate<String, String, String> replyingKafkaTemplate) {
this.replyingKafkaTemplate = replyingKafkaTemplate;
}
public String sendMessageAndGetReply(String requestTopic,
String requestReplyTopic,
String replyTopic,
String requestMessage) {
ProducerRecord<String, String> record = new ProducerRecord<>(requestTopic, requestMessage);
record.headers().add(new RecordHeader(KafkaHeaders.REPLY_TOPIC, requestReplyTopic.getBytes()));
RequestReplyFuture<String, String, String> replyFuture = getReplyingKafkaTemplate()
.sendAndReceive(record);
record.headers().add(new RecordHeader(KafkaHeaders.REPLY_TOPIC, replyTopic.getBytes()));
RequestReplyFuture<String, String, String> replyFuture = replyingKafkaTemplate.sendAndReceive(record);
try {
return Optional.ofNullable(replyFuture.get())

View file

@ -1,20 +0,0 @@
package ru.micord.ervu.kafka.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.kafka.requestreply.ReplyingKafkaTemplate;
import org.springframework.stereotype.Service;
@Service
@Qualifier("journal")
public class JournalReplyingKafkaServiceImpl extends BaseReplyingKafkaServiceImpl {
@Autowired
@Qualifier("journal")
private ReplyingKafkaTemplate<String, String, String> journalReplyingKafkaTemplate;
@Override
protected ReplyingKafkaTemplate<String, String, String> getReplyingKafkaTemplate() {
return journalReplyingKafkaTemplate;
}
}

View file

@ -1,20 +0,0 @@
package ru.micord.ervu.kafka.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.kafka.requestreply.ReplyingKafkaTemplate;
import org.springframework.stereotype.Service;
@Service
@Qualifier("org")
public class OrgReplyingKafkaServiceImpl extends BaseReplyingKafkaServiceImpl {
@Autowired
@Qualifier("org")
private ReplyingKafkaTemplate<String, String, String> orgReplyingKafkaTemplate;
@Override
protected ReplyingKafkaTemplate<String, String, String> getReplyingKafkaTemplate() {
return orgReplyingKafkaTemplate;
}
}

View file

@ -0,0 +1,44 @@
package ru.micord.ervu.s3;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Eduard Tihomirov
*/
@Configuration
public class S3Connection {
@Value("${s3.endpoint}")
private String endpoint;
@Value("${s3.access_key}")
private String accessKey;
@Value("${s3.secret_key}")
private String secretKey;
@Value("${s3.path.style.access.enabled:true}")
private boolean pathStyleAccessEnabled;
@Bean("outClient")
public AmazonS3 getS3OutClient() {
return getS3Client(endpoint, accessKey, secretKey, pathStyleAccessEnabled);
}
private static AmazonS3 getS3Client(String endpoint, String accessKey, String secretKey, Boolean pathStyleAccessEnabled) {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
String region = Region.getRegion(Regions.DEFAULT_REGION).toString();
return AmazonS3ClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, region))
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withPathStyleAccessEnabled(pathStyleAccessEnabled)
.build();
}
}

View file

@ -0,0 +1,48 @@
package ru.micord.ervu.s3;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3URI;
import com.amazonaws.services.s3.model.S3Object;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
/**
* @author Eduard Tihomirov
*/
@Service
public class S3Service {
private final AmazonS3 outClient;
@Autowired
public S3Service(AmazonS3 outClient) {
this.outClient = outClient;
}
public ResponseEntity<Resource> getFile(String fileUrl) {
try {
if (fileUrl == null || fileUrl.isEmpty()) {
return ResponseEntity.noContent().build();
}
AmazonS3URI uri = new AmazonS3URI(fileUrl);
S3Object s3Object = outClient.getObject(uri.getBucket(), uri.getKey());
InputStreamResource resource = new InputStreamResource(s3Object.getObjectContent());
String encodedFilename = URLEncoder.encode(uri.getKey(), StandardCharsets.UTF_8);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFilename)
.contentLength(s3Object.getObjectMetadata().getContentLength())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}
catch (AmazonServiceException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,34 @@
package ru.micord.ervu.security;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import ru.micord.ervu.security.esia.service.EsiaAuthService;
public class LogoutSuccessHandler
implements org.springframework.security.web.authentication.logout.LogoutSuccessHandler {
private final CsrfTokenRepository csrfTokenRepository;
private final EsiaAuthService esiaAuthService;
public LogoutSuccessHandler(CsrfTokenRepository csrfTokenRepository,
EsiaAuthService esiaAuthService) {
this.csrfTokenRepository = csrfTokenRepository;
this.esiaAuthService = esiaAuthService;
}
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
String url = esiaAuthService.logout(request, response);
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(url);
response.getWriter().flush();
CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request);
this.csrfTokenRepository.saveToken(csrfToken, request, response);
}
}

View file

@ -4,59 +4,105 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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 ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
import org.springframework.web.filter.RequestContextFilter;
import ru.micord.ervu.security.esia.service.EsiaAuthService;
import ru.micord.ervu.security.filter.FilterChainExceptionHandler;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthenticationProvider;
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;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public class SecurityConfig {
private static final String[] PERMIT_ALL = new String[] {
"/version", "/esia/url", "/esia/auth", "esia/refresh"
};
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private EsiaAuthService esiaAuthService;
@Autowired
private FilterChainExceptionHandler filterChainExceptionHandler;
@Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(jwtAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
httpConfigure(http);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(new RequestContextFilter(), LogoutFilter.class);
http.addFilterAfter(filterChainExceptionHandler, RequestContextFilter.class);
return http.build();
}
httpConfigure(http);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
protected void httpConfigure(HttpSecurity httpSecurity) throws Exception {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepository.setCookiePath("/");
XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
delegate.setCsrfRequestAttributeName(null);
// Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
// default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
CsrfTokenRequestHandler requestHandler = delegate::handle;
httpSecurity.authorizeHttpRequests(
(authorizeHttpRequests) -> authorizeHttpRequests.requestMatchers(PERMIT_ALL)
.permitAll()
.anyRequest()
.authenticated())
.csrf((csrf) -> csrf.csrfTokenRepository(tokenRepository)
.csrfTokenRequestHandler(requestHandler))
.logout((logout) -> logout.logoutUrl(ESIA_LOGOUT)
.logoutSuccessHandler(new LogoutSuccessHandler(tokenRepository, esiaAuthService)))
.exceptionHandling()
.authenticationEntryPoint(entryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
protected void httpConfigure(HttpSecurity httpSecurity) throws Exception {
String[] permitAll = {"/esia/url", "/esia/auth", "esia/refresh"};
public AuthenticationEntryPoint entryPoint() {
return new UnauthorizedEntryPoint();
}
httpSecurity.authorizeRequests()
.antMatchers(permitAll).permitAll()
.antMatchers("/**").authenticated()
.and()
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(entryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
public AuthenticationEntryPoint entryPoint() {
return new UnauthorizedEntryPoint();
}
@Bean
public SecurityHelper securityHelper() {
return new SecurityHelper();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter("/**",
entryPoint()
);
jwtAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
return jwtAuthenticationFilter;
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper,
AuthenticationManager manager,
JwtTokenService jwtTokenService) {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(
new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper, jwtTokenService);
jwtAuthenticationFilter.setAuthenticationManager(manager);
return jwtAuthenticationFilter;
}
}

View file

@ -0,0 +1,5 @@
package ru.micord.ervu.security;
public class SecurityConstants {
public static final String ESIA_LOGOUT = "/esia/logout";
}

View file

@ -50,6 +50,9 @@ public class EsiaConfig {
@Value("${esia.token.url:aas/oauth2/v3/te}")
private String esiaTokenUrl;
@Value("${esia.upload.data.role}")
private String esiaUploadDataRole;
public String getEsiaOrgScopes() {
String[] scopeItems = esiaOrgScopes.split(",");
return String.join(" ", Arrays.stream(scopeItems).map(item -> orgScopeUrl + item.trim()).toArray(String[]::new));
@ -97,4 +100,8 @@ public class EsiaConfig {
public String getEsiaTokenUrl() {
return esiaTokenUrl;
}
public String getEsiaUploadDataRole() {
return esiaUploadDataRole;
}
}

View file

@ -3,12 +3,14 @@ package ru.micord.ervu.security.esia.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import ru.micord.ervu.security.SecurityConstants;
import ru.micord.ervu.security.esia.model.OrgInfoModel;
import ru.micord.ervu.security.esia.service.EsiaAuthService;
import ru.micord.ervu.security.esia.service.EsiaDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@ -24,38 +26,33 @@ public class EsiaController {
@Autowired
private EsiaDataService esiaDataService;
@RequestMapping(value = "/esia/url")
@GetMapping(value = "/esia/url")
public String getEsiaUrl() {
return esiaAuthService.generateAuthCodeUrl();
}
@RequestMapping(value = "/esia/auth", params = "code", method = RequestMethod.GET)
public boolean esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) {
@GetMapping(value = "/esia/auth", params = "code")
public ResponseEntity<?> esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) {
return esiaAuthService.getEsiaTokensByCode(code, request, response);
}
@RequestMapping(value = "/esia/refresh")
@PostMapping(value = "/esia/refresh")
public void refreshToken(HttpServletRequest request, HttpServletResponse response) {
esiaAuthService.getEsiaTokensByRefreshToken(request, response);
}
@RequestMapping(value = "/esia/org")
@GetMapping(value = "/esia/org")
public OrgInfoModel getOrgInfo(HttpServletRequest request) {
return esiaDataService.getOrgInfo(request);
}
@RequestMapping(value = "/esia/userfullname")
@GetMapping(value = "/esia/userfullname")
public String getUserFullname(HttpServletRequest request) {
return esiaDataService.getUserFullname(request);
}
@RequestMapping(value = "/esia/orgunitname")
@GetMapping(value = "/esia/orgunitname")
public String getOrgUnitName(HttpServletRequest request) {
return esiaDataService.getOrgUnitName(request);
}
@RequestMapping(value = "/esia/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
return esiaAuthService.logout(request, response);
}
}

View file

@ -1,6 +1,7 @@
package ru.micord.ervu.security.esia.service;
import java.io.UnsupportedEncodingException;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
@ -13,7 +14,6 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.Cookie;
@ -22,11 +22,16 @@ import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import ervu.service.okopf.OkopfService;
import ru.micord.ervu.security.esia.token.TokensStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.util.StringUtils;
import ru.micord.ervu.security.esia.config.EsiaConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import ru.micord.ervu.kafka.model.Brhs;
import ru.micord.ervu.kafka.model.Data;
import ru.micord.ervu.kafka.model.Employee;
import ru.micord.ervu.kafka.model.ErvuOrgResponse;
import ru.micord.ervu.kafka.model.OrgInfo;
@ -41,36 +46,32 @@ import ru.micord.ervu.security.esia.model.EsiaAccessToken;
import ru.micord.ervu.security.esia.model.EsiaTokenResponse;
import ru.micord.ervu.security.esia.model.FormUrlencoded;
import ru.micord.ervu.security.esia.model.OrganizationModel;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
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 ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
/**
* @author Eduard Tihomirov
*/
@Service
public class EsiaAuthService {
@Value("${cookie-path:#{null}}")
private String path;
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Autowired
private ObjectMapper objectMapper;
@Autowired
private EsiaConfig esiaConfig;
@Autowired
private UlDataService ulDataService;
@Autowired
private JwtTokenService jwtTokenService;
@Autowired
@Qualifier("org")
private ReplyingKafkaService replyingKafkaService;
@Autowired
private OkopfService okopfService;
@Autowired
private SecurityHelper securityHelper;
@Value("${ervu.kafka.org.reply.topic}")
private String requestReplyTopic;
@ -154,7 +155,7 @@ public class EsiaAuthService {
return uriBuilder.toString();
}
public boolean getEsiaTokensByCode(String esiaAuthCode, HttpServletRequest request, HttpServletResponse response) {
public ResponseEntity<?> getEsiaTokensByCode(String esiaAuthCode, HttpServletRequest request, HttpServletResponse response) {
try {
String clientId = esiaConfig.getClientId();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx");
@ -199,49 +200,48 @@ public class EsiaAuthService {
.build()
.send(postReq, HttpResponse.BodyHandlers.ofString());
String responseString = postResp.body();
EsiaTokenResponse tokenResponse = objectMapper.readValue(responseString, EsiaTokenResponse.class);
if (tokenResponse != null && tokenResponse.getError() != null) {
EsiaTokenResponse tokenResponse = objectMapper.readValue(responseString,
EsiaTokenResponse.class
);
if (tokenResponse == null) {
throw new IllegalStateException("Got empty esia response");
}
if (tokenResponse.getError() != null) {
throw new RuntimeException(tokenResponse.getError_description());
}
String accessToken = tokenResponse.getAccess_token();
boolean hasRole = ulDataService.checkRole(accessToken);
if (!hasRole) {
throw new RuntimeException("The user does not have the required role");
}
String cookiePath = null;
if (path != null) {
cookiePath = path;
}
else {
cookiePath = request.getContextPath();
}
Cookie cookie = new Cookie("access_token", accessToken);
cookie.setHttpOnly(true);
cookie.setPath(cookiePath);
response.addCookie(cookie);
String refreshToken = tokenResponse.getRefresh_token();
Cookie cookieRefresh = new Cookie("refresh_token", refreshToken);
cookieRefresh.setHttpOnly(true);
cookieRefresh.setPath(cookiePath);
response.addCookie(cookieRefresh);
EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken);
String ervuId = getErvuId(accessToken, esiaAccessToken.getSbj_id());
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId);
Cookie authToken = new Cookie("auth_token", token.getValue());
authToken.setPath(cookiePath);
authToken.setHttpOnly(true);
response.addCookie(authToken);
SecurityContextHolder.getContext()
.setAuthentication(
new UsernamePasswordAuthenticationToken(esiaAccessToken.getSbj_id(), null));
Cookie isAuth = new Cookie("webbpm.ervu-lkrp-ul", "true");
isAuth.setMaxAge(tokenResponse.getExpires_in().intValue());
isAuth.setPath("/");
response.addCookie(isAuth);
return true;
String prnOid = esiaAccessToken.getSbj_id();
String refreshToken = tokenResponse.getRefresh_token();
String ervuId = getErvuId(accessToken, prnOid);
Long expiresIn = tokenResponse.getExpires_in();
TokensStore.addAccessToken(prnOid, accessToken, expiresIn);
TokensStore.addRefreshToken(prnOid, refreshToken, expiresIn);
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, hasRole);
int expiry = tokenResponse.getExpires_in().intValue();
Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry);
response.addCookie(accessCookie);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext();
JwtAuthentication authentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
esiaAccessToken.getSbj_id(), token.getValue());
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry);
response.addCookie(authMarkerCookie);
if (!hasRole) {
LOGGER.error("The user with id = " + prnOid + " does not have the required role");
return new ResponseEntity<>(
"Доступ запрещен. Пользователь должен быть включен в группу \"Сотрудник, ответственный за военно-учетную работу\" в ЕСИА",
HttpStatus.FORBIDDEN
);
}
return ResponseEntity.ok("Authentication successful");
}
catch (Exception e) {
throw new RuntimeException(e);
@ -250,15 +250,7 @@ public class EsiaAuthService {
public void getEsiaTokensByRefreshToken(HttpServletRequest request, HttpServletResponse response) {
try {
String refreshToken = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("refresh_token")) {
refreshToken = cookie.getValue();
}
}
}
String refreshToken = jwtTokenService.getRefreshToken(request);
String clientId = esiaConfig.getClientId();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx");
ZonedDateTime dt = ZonedDateTime.now();
@ -307,38 +299,26 @@ public class EsiaAuthService {
throw new RuntimeException(tokenResponse.getError_description());
}
String accessToken = tokenResponse.getAccess_token();
Cookie cookie = new Cookie("access_token", accessToken);
cookie.setHttpOnly(true);
String cookiePath = null;
if (path != null) {
cookiePath = path;
}
else {
cookiePath = request.getContextPath();
}
cookie.setPath(cookiePath);
response.addCookie(cookie);
String newRefreshToken = tokenResponse.getRefresh_token();
Cookie cookieRefresh = new Cookie("refresh_token", newRefreshToken);
cookieRefresh.setHttpOnly(true);
cookieRefresh.setPath(cookiePath);
response.addCookie(cookieRefresh);
EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken);
String ervuId = getErvuId(accessToken, esiaAccessToken.getSbj_id());
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId);
Cookie authToken = new Cookie("auth_token", token.getValue());
authToken.setPath(cookiePath);
authToken.setHttpOnly(true);
response.addCookie(authToken);
SecurityContextHolder.getContext()
.setAuthentication(
new UsernamePasswordAuthenticationToken(esiaAccessToken.getSbj_id(), null));
Cookie isAuth = new Cookie("webbpm.ervu-lkrp-ul", "true");
isAuth.setMaxAge(tokenResponse.getExpires_in().intValue());
isAuth.setPath("/");
response.addCookie(isAuth);
String prnOid = esiaAccessToken.getSbj_id();
Long expiresIn = tokenResponse.getExpires_in();
TokensStore.addAccessToken(prnOid, accessToken, expiresIn);
TokensStore.addRefreshToken(prnOid, newRefreshToken, expiresIn);
String ervuId = getErvuId(accessToken, prnOid);
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, true);
int expiry = tokenResponse.getExpires_in().intValue();
Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry);
response.addCookie(accessCookie);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext();
JwtAuthentication authentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
esiaAccessToken.getSbj_id(), token.getValue());
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry);
response.addCookie(authMarkerCookie);
}
catch (Exception e) {
throw new RuntimeException(e);
@ -379,23 +359,10 @@ public class EsiaAuthService {
public String logout(HttpServletRequest request, HttpServletResponse response) {
try {
Cookie[] cookies = request.getCookies();
if (cookies != null)
for (Cookie cookie : cookies) {
if (cookie.getName().equals("webbpm.ervu-lkrp-ul")) {
cookie.setValue("");
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
}
else if (cookie.getName().equals("auth_token") || cookie.getName().equals("refresh_token")
|| cookie.getName().equals("access_token")) {
cookie.setValue("");
cookie.setPath(cookie.getPath());
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
securityHelper.clearAccessCookies(response);
String userId = jwtTokenService.getUserAccountId(request);
TokensStore.removeAccessToken(userId);
TokensStore.removeRefreshToken(userId);
String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl();
String redirectUrl = esiaConfig.getRedirectUrl();
URL url = new URL(logoutUrl);
@ -420,26 +387,23 @@ public class EsiaAuthService {
requestReplyTopic, objectMapper.writeValueAsString(orgInfo)
);
ErvuOrgResponse ervuOrgResponse = objectMapper.readValue(kafkaResponse, ErvuOrgResponse.class);
List<String> listErvuId = Arrays.stream(ervuOrgResponse.getData()).filter(data -> data.getPrnOid().equals(prnOid)).map(
Data::getOrgId_ERVU).toList();
if (listErvuId.size() > 1) {
throw new RuntimeException("More than one ervuId for prnOid = " + prnOid);
}
else if (listErvuId.isEmpty()) {
String ervuId = ervuOrgResponse.getData().getOrgId_ERVU();
if (!StringUtils.hasText(ervuId)) {
throw new RuntimeException("No ervuId for prnOid = " + prnOid);
}
return listErvuId.get(0);
return ervuId;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private OrgInfo copyToOrgInfo(OrganizationModel organizationModel, EmployeeModel employeeModel, EmployeeModel chiefModel ) {
OrgInfo orgInfo = new OrgInfo();
orgInfo.setChiefInfo(copyToEmployee(chiefModel));
if (chiefModel != null) {
orgInfo.setChiefInfo(copyToEmployee(chiefModel));
}
orgInfo.setSenderInfo(copyToEmployee(employeeModel));
orgInfo.setBrhs(
Arrays.stream(organizationModel.getBrhs()).map(brhsModel -> {
@ -448,8 +412,8 @@ public class EsiaAuthService {
brhs.setBrhOid(brhsModel.getBrhOid());
brhs.setKpp(brhsModel.getKpp());
brhs.setLeg(brhsModel.getLeg());
brhs.setAddresses(brhsModel.getAddresses().getElements());
brhs.setContacts(brhsModel.getContacts().getElements());
brhs.setAddresses(brhsModel.getAddresses() != null ? brhsModel.getAddresses().getElements() : null);
brhs.setContacts(brhsModel.getContacts() != null ? brhsModel.getContacts().getElements() : null);
return brhs;
}).toArray(Brhs[]::new));
orgInfo.setAddresses(organizationModel.getAddresses().getElements());

View file

@ -1,12 +1,18 @@
package ru.micord.ervu.security.esia.service;
import java.util.Arrays;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ru.micord.ervu.security.esia.model.*;
import ru.micord.ervu.security.esia.model.Addresses;
import ru.micord.ervu.security.esia.model.Contacts;
import ru.micord.ervu.security.esia.model.EmployeeModel;
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
import ru.micord.ervu.security.esia.model.OrgInfoModel;
import ru.micord.ervu.security.esia.model.OrganizationModel;
import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
/**
* @author Eduard Tihomirov
@ -17,8 +23,11 @@ public class EsiaDataService {
@Autowired
private UlDataService ulDataService;
@Autowired
private JwtTokenService jwtTokenService;
public OrgInfoModel getOrgInfo(HttpServletRequest request) {
String accessToken = getAccessToken(request);
String accessToken = jwtTokenService.getAccessToken(request);
if (accessToken == null) {
return null;
}
@ -44,10 +53,12 @@ public class EsiaDataService {
}
} );
}
orgInfoModel.chiefFullname =
chiefEmployeeModel.getPerson().getLastName() + " " + chiefEmployeeModel.getPerson()
.getFirstName() + " " + chiefEmployeeModel.getPerson().getMiddleName();
orgInfoModel.chiefPosition = chiefEmployeeModel.getPosition();
if (chiefEmployeeModel != null) {
orgInfoModel.chiefFullname =
chiefEmployeeModel.getPerson().getLastName() + " " + chiefEmployeeModel.getPerson()
.getFirstName() + " " + chiefEmployeeModel.getPerson().getMiddleName();
orgInfoModel.chiefPosition = chiefEmployeeModel.getPosition();
}
orgInfoModel.ogrn = organizationModel.getOgrn();
orgInfoModel.kpp = organizationModel.getKpp();
orgInfoModel.inn = organizationModel.getInn();
@ -67,7 +78,7 @@ public class EsiaDataService {
}
public String getOrgUnitName(HttpServletRequest request) {
String accessToken = getAccessToken(request);
String accessToken = jwtTokenService.getAccessToken(request);
if (accessToken == null) {
return null;
}
@ -76,7 +87,7 @@ public class EsiaDataService {
}
public String getUserFullname(HttpServletRequest request) {
String accessToken = getAccessToken(request);
String accessToken = jwtTokenService.getAccessToken(request);
if (accessToken == null) {
return null;
}
@ -84,16 +95,4 @@ public class EsiaDataService {
PersonModel personModel = ulDataService.getPersonData(esiaAccessToken.getSbj_id(), accessToken);
return personModel.getLastName() + " " + personModel.getFirstName().charAt(0) + ". " + personModel.getMiddleName().charAt(0) + ".";
}
private String getAccessToken(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("access_token")) {
return cookie.getValue();
}
}
}
return null;
}
}

View file

@ -210,7 +210,7 @@ public class UlDataServiceImpl implements UlDataService {
.build()
.send(getReq, HttpResponse.BodyHandlers.ofString());
errorHandler(getResp);
if (getResp.body().contains("MNSV89_UPLOAD_DATA")) {
if (getResp.body().contains(esiaConfig.getEsiaUploadDataRole())) {
return true;
}
else {

View file

@ -0,0 +1,34 @@
package ru.micord.ervu.security.esia.token;
/**
* @author Eduard Tihomirov
*/
public class ExpiringToken {
private String accessToken;
private long expiryTime;
public ExpiringToken(String accessToken, long expiryTime) {
this.accessToken = accessToken;
this.expiryTime = expiryTime;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public long getExpiryTime() {
return expiryTime;
}
public void setExpiryTime(long expiryTime) {
this.expiryTime = expiryTime;
}
boolean isExpired() {
return System.currentTimeMillis() > expiryTime;
}
}

View file

@ -0,0 +1,20 @@
package ru.micord.ervu.security.esia.token;
import net.javacrumbs.shedlock.core.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Eduard Tihomirov
*/
@Service
public class TokensClearShedulerService {
@Scheduled(cron = "${esia.token.clear.cron:0 0 */1 * * *}")
@SchedulerLock(name = "clearToken")
@Transactional
public void load() {
TokensStore.removeExpiredRefreshToken();
TokensStore.removeExpiredAccessToken();
}
}

View file

@ -0,0 +1,61 @@
package ru.micord.ervu.security.esia.token;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Eduard Tihomirov
*/
public class TokensStore {
private static final Map<String, ExpiringToken> accessTokensMap = new ConcurrentHashMap<>();
private static final Map<String, ExpiringToken> refreshTokensMap = new ConcurrentHashMap<>();
public static void addAccessToken(String prnOid, String token, long expiresIn) {
if (token != null) {
long expiryTime = System.currentTimeMillis() + 1000L * expiresIn;
accessTokensMap.put(prnOid, new ExpiringToken(token, expiryTime));
}
}
public static String getAccessToken(String prnOid) {
return accessTokensMap.get(prnOid).getAccessToken();
}
public static void removeExpiredAccessToken() {
for (String key : accessTokensMap.keySet()) {
ExpiringToken token = accessTokensMap.get(key);
if (token != null && token.isExpired()) {
accessTokensMap.remove(key);
}
}
}
public static void removeExpiredRefreshToken() {
for (String key : refreshTokensMap.keySet()) {
ExpiringToken token = refreshTokensMap.get(key);
if (token != null && token.isExpired()) {
refreshTokensMap.remove(key);
}
}
}
public static void removeAccessToken(String prnOid) {
accessTokensMap.remove(prnOid);
}
public static void addRefreshToken(String prnOid, String token, long expiresIn) {
if (token != null) {
long expiryTime = System.currentTimeMillis() + 1000L * expiresIn;
refreshTokensMap.put(prnOid, new ExpiringToken(token, expiryTime));
}
}
public static String getRefreshToken(String prnOid) {
return refreshTokensMap.get(prnOid).getAccessToken();
}
public static void removeRefreshToken(String prnOid) {
refreshTokensMap.remove(prnOid);
}
}

View file

@ -0,0 +1,29 @@
package ru.micord.ervu.security.filter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
@Component
public class FilterChainExceptionHandler extends OncePerRequestFilter {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) {
try {
filterChain.doFilter(request, response);
}
catch (Exception e) {
resolver.resolveException(request, response, null, e);
}
}
}

View file

@ -1,5 +1,7 @@
package ru.micord.ervu.security.webbpm.jwt;
import java.util.Collections;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
@ -44,10 +46,12 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
throw new BadCredentialsException("Auth token is not valid for user " + token.getUserAccountId());
}
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
UsernamePasswordAuthenticationToken pwdToken =
UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null,
Collections.emptyList()
);
return new JwtAuthentication(usernamePasswordAuthenticationToken, token.getUserAccountId());
return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
}
@Override

View file

@ -0,0 +1,33 @@
package ru.micord.ervu.security.webbpm.jwt;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken;
public final class JwtMatcher implements RequestMatcher {
private final Set<AntPathRequestMatcher> excludedPathMatchers;
private final AntPathRequestMatcher securedMatcher;
public JwtMatcher(String securedPath, String... excludedPaths) {
this.securedMatcher = new AntPathRequestMatcher(securedPath);
this.excludedPathMatchers = Arrays.stream(excludedPaths)
.map(AntPathRequestMatcher::new)
.collect(Collectors.toSet());
}
@Override
public boolean matches(HttpServletRequest request) {
if (this.excludedPathMatchers.stream().anyMatch(matcher -> matcher.matches(request))) {
return false;
}
else {
return extractAuthToken(request) != null && this.securedMatcher.matches(request);
}
}
}

View file

@ -4,10 +4,10 @@ import java.io.IOException;
import java.lang.invoke.MethodHandles;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import io.jsonwebtoken.ExpiredJwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.CredentialsExpiredException;
@ -16,69 +16,80 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper;
import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken;
/**
* @author Flyur Karimov
*/
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Logger LOGGER = LoggerFactory.getLogger(
MethodHandles.lookup().lookupClass());
private final AuthenticationEntryPoint entryPoint;
public JwtAuthenticationFilter(String securityPath, AuthenticationEntryPoint entryPoint) {
super(securityPath);
private final SecurityHelper securityHelper;
private final JwtTokenService jwtTokenService;
public JwtAuthenticationFilter(RequestMatcher requestMatcher,
AuthenticationEntryPoint entryPoint,
SecurityHelper securityHelper,
JwtTokenService jwtTokenService) {
super(requestMatcher);
this.entryPoint = entryPoint;
this.securityHelper = securityHelper;
this.jwtTokenService = jwtTokenService;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws AuthenticationException {
String token = extractAuthTokenFromRequest(httpServletRequest);
HttpServletResponse httpServletResponse)
throws AuthenticationException {
String tokenStr = extractAuthToken(httpServletRequest);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
authentication = new JwtAuthentication(null, null, token);
authentication = new JwtAuthentication(null, null, tokenStr);
}
try {
authentication = getAuthenticationManager().authenticate(authentication);
if (!httpServletRequest.getRequestURI().endsWith("esia/logout")) {
Token token = jwtTokenService.getToken(tokenStr);
if (!token.getHasRole()) {
throw new CredentialsExpiredException("Invalid token. User has no required role");
}
}
}
catch (CredentialsExpiredException e) {
securityHelper.clearAccessCookies(httpServletResponse);
httpServletResponse.setStatus(401);
LOGGER.warn(e.getMessage());
return null;
}
return authentication;
}
@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return extractAuthTokenFromRequest(request) != null;
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException {
FilterChain chain, Authentication authentication)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
LOGGER.error("Jwt unsuccessful authentication exception", exception);
SecurityContextHolder.clearContext();
entryPoint.commence(request, response, exception);
}
public String extractAuthTokenFromRequest(HttpServletRequest httpRequest) {
String token = null;
Cookie[] cookies = httpRequest.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("auth_token")) {
token = cookie.getValue();
}
}
}
return token;
}
}

View file

@ -0,0 +1,43 @@
package ru.micord.ervu.security.webbpm.jwt.helper;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.AUTH_MARKER;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.AUTH_TOKEN;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.createCookie;
public final class SecurityHelper {
@Value("${cookie.path:#{null}}")
private String accessCookiePath;
public void clearAccessCookies(HttpServletResponse response) {
Cookie tokenCookie = createCookie(AUTH_TOKEN, null, null);
tokenCookie.setMaxAge(0);
tokenCookie.setPath(accessCookiePath);
tokenCookie.setHttpOnly(true);
response.addCookie(tokenCookie);
Cookie markerCookie = createCookie(AUTH_MARKER, null, null);
markerCookie.setMaxAge(0);
markerCookie.setPath("/");
response.addCookie(markerCookie);
}
public Cookie createAccessCookie(String cookieValue, int expiry) {
Cookie authToken = createCookie(SecurityUtil.AUTH_TOKEN, cookieValue, accessCookiePath);
authToken.setPath(accessCookiePath);
authToken.setMaxAge(expiry);
return authToken;
}
public Cookie createAuthMarkerCookie(String cookieValue, int expiry) {
Cookie marker = createCookie(AUTH_MARKER, cookieValue, "/");
marker.setMaxAge(expiry);
marker.setHttpOnly(false);
return marker;
}
}

View file

@ -7,12 +7,14 @@ public class Token {
private final String issuer;
private final Date expirationDate;
private final String value;
private final Boolean hasRole;
public Token(String userAccountId, String issuer, Date expirationDate, String value) {
public Token(String userAccountId, String issuer, Date expirationDate, String value, Boolean hasRole) {
this.userAccountId = userAccountId;
this.issuer = issuer;
this.expirationDate = expirationDate;
this.value = value;
this.hasRole = hasRole;
}
public String getUserAccountId() {
@ -34,4 +36,8 @@ public class Token {
public String getValue() {
return value;
}
public Boolean getHasRole() {
return hasRole;
}
}

View file

@ -4,6 +4,7 @@ import java.lang.invoke.MethodHandles;
import java.util.Base64;
import java.util.Date;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
@ -13,10 +14,13 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import ru.micord.ervu.security.esia.token.TokensStore;
import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken;
/**
* @author Flyur Karimov
*/
@ -38,7 +42,7 @@ public class JwtTokenService {
this.SIGNING_KEY = Keys.hmacShaKeyFor(encodedKey);
}
public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId) {
public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId, Boolean hasRole) {
Date expirationDate = new Date(System.currentTimeMillis() + 1000L * expiresIn);
String value = Jwts.builder()
@ -46,9 +50,10 @@ public class JwtTokenService {
.setIssuer(tokenIssuerName)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(expirationDate)
.claim("hasRole", hasRole)
.signWith(SIGNING_KEY)
.compact();
return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value);
return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value, hasRole);
}
public boolean isValid(Token token) {
@ -70,6 +75,26 @@ public class JwtTokenService {
.parseClaimsJws(token)
.getBody();
return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token);
return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token, claims.get("hasRole", Boolean.class));
}
public String getAccessToken(HttpServletRequest request) {
return TokensStore.getAccessToken(getUserAccountId(request));
}
public String getRefreshToken(HttpServletRequest request) {
return TokensStore.getRefreshToken(getUserAccountId(request));
}
public String getUserAccountId(HttpServletRequest request) {
String authToken = extractAuthToken(request);
if (authToken != null) {
String[] ids = getToken(authToken).getUserAccountId().split(":");
return ids[0];
}
else {
throw new RuntimeException("Failed to get auth data. User unauthorized.");
}
}
}

View file

@ -0,0 +1,45 @@
package ru.micord.ervu.security.webbpm.jwt.util;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.util.WebUtils;
import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;
public final class SecurityUtil {
public static final String AUTH_TOKEN = "auth_token";
public static final String AUTH_MARKER = "webbpm.ervu-lkrp-ul";
private SecurityUtil() {
//empty
}
public static Cookie createCookie(String name, String value, String path) {
String cookieValue = value != null ? URLEncoder.encode(value, StandardCharsets.UTF_8) : null;
Cookie cookie = new Cookie(name, cookieValue);
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
REFERENCE_REQUEST);
if (path != null) {
cookie.setPath(path);
}
else {
cookie.setPath(request.getContextPath());
}
cookie.setHttpOnly(true);
return cookie;
}
public static String extractAuthToken(HttpServletRequest httpRequest) {
Cookie cookie = WebUtils.getCookie(httpRequest, AUTH_TOKEN);
return cookie != null ? cookie.getValue() : null;
}
}

View file

@ -13,4 +13,8 @@ public interface InteractionService {
List<InteractionLogRecord> get(String ervuId, String[] excludedStatuses);
void setStatus(String fileId, String status, String fileName, String form, Timestamp timestamp, String sender, Integer count, String ervuId);
void updateStatus(String fileId, String status, String ervuId);
void delete(String fileId, String ervuId);
}

View file

@ -42,6 +42,22 @@ public class InteractionServiceImpl implements InteractionService {
.set(INTERACTION_LOG.SENDER, sender)
.set(INTERACTION_LOG.FILE_NAME, fileName)
.set(INTERACTION_LOG.RECORDS_SENT, count)
.set(INTERACTION_LOG.ERVU_ID, ervuId);
.set(INTERACTION_LOG.ERVU_ID, ervuId)
.execute();
}
public void updateStatus(String fileId, String status, String ervuId) {
dslContext.update(INTERACTION_LOG)
.set(INTERACTION_LOG.STATUS, status)
.where(INTERACTION_LOG.ERVU_ID.eq(ervuId))
.and(INTERACTION_LOG.FILE_ID.eq(fileId))
.execute();
}
public void delete(String fileId, String ervuId) {
dslContext.deleteFrom(INTERACTION_LOG)
.where(INTERACTION_LOG.ERVU_ID.eq(ervuId)
.and(INTERACTION_LOG.FILE_ID.eq(fileId)))
.execute();
}
}

View file

@ -1,22 +1,22 @@
package ru.micord.ervu.service.grid.impl;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import ru.micord.ervu.journal.JournalDto;
import ru.micord.ervu.journal.JournalFileDataRequest;
import ru.micord.ervu.journal.JournalFileDataResponse;
import ru.micord.ervu.journal.mapper.JournalDtoMapper;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
import ru.micord.ervu.service.InteractionService;
import ru.micord.ervu.service.grid.InMemoryStaticGridLoadService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import ru.micord.ervu.kafka.service.ReplyingKafkaService;
@ -28,8 +28,9 @@ public class JournalInMemoryStaticGridLoadService implements
private final JwtTokenService jwtTokenService;
private final InteractionService interactionService;
private final ReplyingKafkaService ervuReplyingKafkaService;
private final ReplyingKafkaService replyingKafkaService;
private final ObjectMapper objectMapper;
private final HttpServletRequest request;
@Value("${ervu.kafka.journal.request.topic}")
private String requestTopic;
@ -40,12 +41,13 @@ public class JournalInMemoryStaticGridLoadService implements
public JournalInMemoryStaticGridLoadService(JwtTokenService jwtTokenService,
InteractionService interactionService,
@Qualifier("journal") ReplyingKafkaService ervuReplyingKafkaService,
ObjectMapper objectMapper) {
ReplyingKafkaService replyingKafkaService,
ObjectMapper objectMapper, HttpServletRequest request) {
this.jwtTokenService = jwtTokenService;
this.interactionService = interactionService;
this.ervuReplyingKafkaService = ervuReplyingKafkaService;
this.replyingKafkaService = replyingKafkaService;
this.objectMapper = objectMapper;
this.request = request;
}
@Override
@ -58,7 +60,7 @@ public class JournalInMemoryStaticGridLoadService implements
List<JournalDto> ervuJournalList;
try {
String responseJsonString = ervuReplyingKafkaService.sendMessageAndGetReply(requestTopic,
String responseJsonString = replyingKafkaService.sendMessageAndGetReply(requestTopic,
replyTopic, objectMapper.writeValueAsString(journalFileDataRequest));
JournalFileDataResponse journalFileDataResponse = objectMapper.readValue(responseJsonString,
JournalFileDataResponse.class);
@ -76,11 +78,14 @@ public class JournalInMemoryStaticGridLoadService implements
}
private JournalFileDataRequest initJournalFileDataRequest() {
Optional<Authentication> authentication = Optional.ofNullable(
SecurityContextHolder.getContext().getAuthentication());
String jwtToken = authentication.map(auth -> ((JwtAuthentication) auth).getToken())
String authToken = Optional.ofNullable(request.getCookies())
.map(cookies -> Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals("auth_token"))
.findFirst()
.map(Cookie::getValue)
.orElseThrow(() -> new RuntimeException("Failed to get auth data. User unauthorized.")))
.orElseThrow(() -> new RuntimeException("Failed to get auth data. User unauthorized."));
String[] ids = jwtTokenService.getToken(jwtToken).getUserAccountId().split(":");
String[] ids = jwtTokenService.getToken(authToken).getUserAccountId().split(":");
String userId = ids[0];
String ervuId = ids[1];
return new JournalFileDataRequest()

View file

@ -14,7 +14,7 @@ import static org.springframework.util.StringUtils.hasText;
public final class DateUtil {
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");
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");
private DateUtil() {}

View file

@ -677,14 +677,6 @@ JBPM использует 3 корневых категории логирова
</logger>
```
# Взаимодействие с ЕСНСИ в части получения справочника ОКОПФ
Свойства задаются в файле config/standalone/dev/standalone.xml
## Параметры
- `esnsi.okopf.url` - url который обращается к еснси для получения справочника и скачивает данные спровочников организации в виде заархивированного json файла.
- `esnsi.okopf.cron.load` - настройка, которая указывет расписание для загрузки справочника окопф и сохранение данных по справкам в БД
# Описание параметров конфигурации клиентской части
Свойства задаются в файле frontend/src/resources/app-config.json или frontend.war/src/resources/app-config.json
@ -756,3 +748,75 @@ JBPM использует 3 корневых категории логирова
1. Смените адрес NPM registry в файле frontend.npmrc. Пример - registry=https://repo.example.com/repository/npm-all/
2. Поменяйте ссылки в блоке , файла pom.xml
#### Подключение базы данных
- `DB_APP_USERNAME` - логин пользователя для подключения к базе данных.
- `DB_APP_PASSWORD` - пароль пользователя для доступа к базе данных.
- `DB_APP_HOST` - адрес сервера базы данных.
- `DB_APP_PORT` - порт, на котором осуществляется подключение к базе данных.
- `DB_APP_NAME` - имя базы данных, к которой необходимо подключиться.
#### Аутентификация через ЕСИА
- `ESIA_SCOPES` - область доступа, позволяющие получить данные о пользователе. Тип данных, к которым система-клиент намерена получить доступ
- `ESIA_ORG_SCOPES` - область доступа, позволяющие получить данные о организации. Тип данных, к которым система-клиент намерена получить доступ
- `ESIA_ORG_SCOPE_URL` - обязательный префикс областей доступа, для получения данных об организации.
- `ESIA_BASE_URI` - базовый url для обращения в ЕСИА.
- `ESIA_CLIENT_ID` - идентификатор системы-клиента (мнемоника системы в ЕСИА указанная прописными буквами)
- `ESIA_REDIRECT_URL` - ссылка, по которой должен быть направлен пользователь
после того, как ЕСИА даст разрешение на доступ к ресурсу.
Важно: ESIA_REDIRECT_URL должна содержать полный адрес вплоть до последнего слэша
- https://lkul.ervu.loc/ - правильное значение параметра
- https://lkul.ervu.loc - неправильное значение параметра
- `ESIA_UPLOAD_DATA_ROLE` - мнемоника группы, для роли "Сотрудник, ответственный за военно-учетную работу".
- `SIGN_URL` - url для подписания с помощью КриптоПро секрета клиента, необходимого для аутентификации через ЕСИА.
- `ESIA_CLIENT_CERT_HASH` - параметр, содержащий хэш сертификата (fingerprint сертификата) системы-клиента в hexформате.
#### Взаимодействие с ЕСНСИ в части получения справочника ОКОПФ
- `ESNSI_OKOPF_URL` - url который обращается к еснси для получения справочника и скачивает данные спровочников организации в виде заархивированного json файла.
- `ESNSI_OKOPF_CRON_LOAD` - настройка, которая указывет расписание для загрузки справочника окопф и сохранение данных по справкам в БД
#### Взаимодействие с WebDav
- `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
- `AV_KAFKA_BOOTSTRAP_SERVERS` - список пар хост:порт, использующихся для установки первоначального соединения с кластером Kafka
- `AV_KAFKA_SECURITY_PROTOCOL` - протокол, используемый для взаимодействия с брокерами
- `AV_KAFKA_SASL_MECHANISM` - механизм SASL, используемый для клиентских подключений
- `AV_KAFKA_USERNAME` - пользователь для подключения к Kafka
- `AV_KAFKA_PASSWORD` - пароль для подключения к Kafka
- `AV_KAFKA_GROUP_ID` - идентификатор группы потребителей, который отвечает за создание группы для объединения нескольких потребителей
- `AV_KAFKA_MESSAGE_TOPIC_NAME` - топик для записи данных по файлу для перекладчика.
- `AV_KAFKA_DOWNLOAD_RESPONSE` - топик для чтения статусов файла, полученных от перекладчика.
#### Взаимодействие с Kafka ERVU
- `ERVU_KAFKA_BOOTSTRAP_SERVERS` - список пар хост:порт, использующихся для установки первоначального соединения с кластером Kafka
- `ERVU_KAFKA_SECURITY_PROTOCOL` - протокол, используемый для взаимодействия с брокерами
- `ERVU_KAFKA_SASL_MECHANISM` - механизм SASL, используемый для клиентских подключений
- `ERVU_KAFKA_USERNAME` - пользователь для подключения к Kafka
- `ERVU_KAFKA_PASSWORD` - пароль для подключения к Kafka
- `ERVU_KAFKA_GROUP_ID` - идентификатор группы потребителей, который отвечает за создание группы для объединения нескольких потребителей
- `ERVU_KAFKA_REPLY_TIMEOUT` - определяет, сколько времени Kafka будет ожидать ответа от потребителя после отправки сообщения. Значение задается в секундах
- `ERVU_KAFKA_ORG_REQUEST_TOPIC` - топик для записи данных об организации, для получения id организации из ЕРВУ.
- `ERVU_KAFKA_ORG_REPLY_TOPIC` - топик для чтения id организации из ЕРВУ.
- `ERVU_KAFKA_JOURNAL_REQUEST_TOPIC` - топик для записи запроса для получения данных по журналу взаимодействия
- `ERVU_KAFKA_JOURNAL_REPLY_TOPIC` - топик для чтения данных по журналу взаимодействия
- `ERVU_KAFKA_EXCERPT_REQUEST_TOPIC` - топик для записи запроса для получения выписки по журналу взаимодействия
- `ERVU_KAFKA_EXCERPT_REPLY_TOPIC` - топик для чтения выписки по журналу взаимодействия. Содержит ссылку на S3 с файлом выписки
- `DB.JOURNAL.EXCLUDED.STATUSES` - статусы файла, которые необходимо исключить при получении данных по журналу взаимодействия из базы данных приложения
#### Взаимодействие с S3
- `S3_ENDPOINT` - url для подключения к S3
- `S3_ACCESS_KEY` - публичная часть учетных данных AWS
- `S3_SECRET_KEY` - закрытая часть пары ключей AWS

View file

@ -9,15 +9,14 @@ 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_SEND_MESSAGE_TOPIC_NAME=file-to-upload
AV_KAFKA_SEND_URL=http://10.10.31.11:32609
AV_KAFKA_SEND_SECURITY_PROTOCOL=SASL_PLAINTEXT
AV_KAFKA_MESSAGE_TOPIC_NAME=file-to-upload
AV_KAFKA_BOOTSTRAP_SERVERS=http://10.10.31.11:32609
AV_KAFKA_SECURITY_PROTOCOL=SASL_PLAINTEXT
AV_KAFKA_SASL_MECHANISM=SCRAM-SHA-256
AV_KAFKA_SEND_USERNAME=user1
AV_KAFKA_SEND_PASSWORD=Blfi9d2OFG
ERVU_FILEUPLOAD_MAX_FILE_SIZE=5242880
ERVU_FILEUPLOAD_MAX_REQUEST_SIZE=6291456
ERVU_FILEUPLOAD_FILE_SIZE_THRESHOLD=0
AV_KAFKA_USERNAME=user1
AV_KAFKA_PASSWORD=Blfi9d2OFG
AV_KAFKA_GROUP_ID=1
AV_KAFKA_DOWNLOAD_RESPONSE=ervu.lkrp.av-fileupload-status
ESIA_SCOPES=fullname, snils, id_doc, birthdate, usr_org, openid
ESIA_ORG_SCOPES=org_fullname, org_shortname, org_brhs, org_brhs_ctts, org_brhs_addrs, org_type, org_ogrn, org_inn, org_leg, org_kpp, org_ctts, org_addrs, org_grps, org_emps
@ -25,18 +24,33 @@ ESIA_ORG_SCOPE_URL=http://esia.gosuslugi.ru/
ESIA_BASE_URI=https://esia-portal1.test.gosuslugi.ru/
ESIA_CLIENT_ID=MNSV89
ESIA_REDIRECT_URL=https://lkrp-dev.micord.ru/ul/
ESIA_UPLOAD_DATA_ROLE=MNSV89_UPLOAD_DATA
SIGN_URL=https://ervu-sign-dev.k8s.micord.ru/sign
ESIA_CLIENT_CERT_HASH=04508B4B0B58776A954A0E15F574B4E58799D74C61EE020B3330716C203E3BDD
ERVU_KAFKA_BOOTSTRAP_SERVERS=localhost:9092
ERVU_KAFKA_BOOTSTRAP_SERVERS=10.10.31.11:32609
ERVU_KAFKA_ORG_REPLY_TOPIC=ervu.organization.response
ERVU_KAFKA_GROUP_ID=1
ERVU_KAFKA_ORG_REQUEST_TOPIC=ervu.organization.request
ERVU_KAFKA_REPLY_TIMEOUT=30
ERVU_KAFKA_JOURNAL_REQUEST_TOPIC=ervu.organization.journal.request
ERVU_KAFKA_JOURNAL_REPLY_TOPIC=ervu.organization.journal.response
DB.JOURNAL.EXCLUDED.STATUSES=Направлено в ЕРВУ,Получен ЕРВУ
ESNSI_OKOPF_URL=https://esnsi.gosuslugi.ru/rest/ext/v1/classifiers/11465/file?extension=JSON&encoding=UTF_8
ESNSI_OKOPF_CRON_LOAD=0 0 */1 * * *
ERVU_KAFKA_SEND_SECURITY_PROTOCOL=SASL_PLAINTEXT
ERVU_KAFKA_SECURITY_PROTOCOL=SASL_PLAINTEXT
ERVU_KAFKA_SASL_MECHANISM=SCRAM-SHA-256
ERVU_KAFKA_SEND_USERNAME=user1
ERVU_KAFKA_SEND_PASSWORD=Blfi9d2OFG
ERVU_KAFKA_USERNAME=user1
ERVU_KAFKA_PASSWORD=Blfi9d2OFG
ERVU_KAFKA_EXCERPT_REPLY_TOPIC=ervu.lkrp.excerpt.response
ERVU_KAFKA_EXCERPT_REQUEST_TOPIC=ervu.lkrp.excerpt.request
ERVU_FILE_UPLOAD_MAX_FILE_SIZE=5242880
ERVU_FILE_UPLOAD_MAX_REQUEST_SIZE=6291456
ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD=0
S3_ENDPOINT=http://ervu-minio.k8s.micord.ru:31900
S3_ACCESS_KEY=rlTdTvkmSXu9FsLhfecw
S3_SECRET_KEY=NUmY0wwRIEyAd98GCKd1cOgJWvLQYAcMMul5Ulu0
ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * *
COOKIE_PATH=/ul

View file

@ -58,15 +58,12 @@
<property name="file.webdav.upload.username" value="test"/>
<property name="file.webdav.upload.password" value="test"/>
<property name="av.kafka.download-request-topic" value="ervu.lkrp.download.request"/>
<property name="av.kafka.send.url" value="localhost:9092"/>
<property name="av.kafka.send.security.protocol" value="SASL_PLAINTEXT"/>
<property name="av.kafka.bootstrap.servers" value="localhost:9092"/>
<property name="av.kafka.security.protocol" value="SASL_PLAINTEXT"/>
<property name="av.kafka.sasl.mechanism" value="SCRAM-SHA-256"/>
<property name="av.kafka.send.username" value="user1"/>
<property name="av.kafka.send.password" value="Blfi9d2OFG"/>
<property name="av.kafka.send.message.topic.name" value="file-to-upload"/>
<property name="ervu.fileupload.max_file_size" value="5242880"/>
<property name="ervu.fileupload.max_request_size" value="6291456"/>
<property name="ervu.fileupload.file_size_threshold" value="0"/>
<property name="av.kafka.username" value="user1"/>
<property name="av.kafka.password" value="Blfi9d2OFG"/>
<property name="av.kafka.message.topic.name" value="file-to-upload"/>
<property name="esia.scopes" value="fullname, snils, id_doc, birthdate, usr_org, openid"/>
<property name="esia.org.scopes" value="org_fullname, org_shortname, org_brhs, org_brhs_ctts, org_brhs_addrs, org_type, org_ogrn, org_inn, org_leg, org_kpp, org_ctts, org_addrs, org_grps, org_emps"/>
<property name="esia.org.scope.url" value="http://esia.gosuslugi.ru/"/>
@ -81,14 +78,24 @@
<property name="ervu.kafka.reply.timeout" value="30"/>
<property name="esia.client.cert.hash" value="04508B4B0B58776A954A0E15F574B4E58799D74C61EE020B3330716C203E3BDD"/>
<property name="bpmn.enable" value="false"/>
<property name="ervu.kafka.send.security.protocol" value="SASL_PLAINTEXT"/>
<property name="ervu.kafka.security.protocol" value="SASL_PLAINTEXT"/>
<property name="ervu.kafka.sasl.mechanism" value="SCRAM-SHA-256"/>
<property name="ervu.kafka.send.username" value="user1"/>
<property name="ervu.kafka.send.password" value="Blfi9d2OFG"/>
<property name="ervu.kafka.username" value="user1"/>
<property name="ervu.kafka.password" value="Blfi9d2OFG"/>
<property name="esnsi.okopf.cron.load" value="0 0 */1 * * *"/>
<property name="esnsi.okopf.url" value="https://esnsi.gosuslugi.ru/rest/ext/v1/classifiers/11465/file?extension=JSON&amp;encoding=UTF_8"/>
<property name="ervu.kafka.journal.request.topic" value="ervu.organization.journal.request"/>
<property name="ervu.kafka.journal.reply.topic" value="ervu.organization.journal.response"/>
<property name="db.journal.excluded.statuses" value="Направлено в ЕРВУ,Получен ЕРВУ"/>
<property name="ervu.kafka.excerpt.reply.topic" value="ervu.lkrp.excerpt.response"/>
<property name="ervu.kafka.excerpt.request.topic" value="ervu.lkrp.excerpt.request"/>
<property name="s3.endpoint" value="http://ervu-minio.k8s.micord.ru:31900"/>
<property name="s3.access_key" value="rlTdTvkmSXu9FsLhfecw"/>
<property name="s3.secret_key" value="NUmY0wwRIEyAd98GCKd1cOgJWvLQYAcMMul5Ulu0"/>
<property name="av.kafka.group.id" value="1"/>
<property name="av.kafka.download.response" value="ervu.lkrp.av-fileupload-status"/>
<property name="esia.token.clear.cron" value="0 0 */1 * * *"/>
<property name="esia.upload.data.role" value="MNSV89_UPLOAD_DATA"/>
</system-properties>
<management>
<audit-log>

View file

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

111
frontend/home.html Normal file
View file

@ -0,0 +1,111 @@
<html>
<head>
<title></title>
<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">
</head>
<body class="ul">
<div class="header">
<div class="header-logo"></div>
<div class="header-title">Официальный сайт Минобороны России <a href="https://mil.ru/">mil.ru</a></div>
</div>
<div class="container">
<div class="container-inside">
<div class="list-group lk-what">
<div>
<div class="title short-text"><a href="/">Личный кабинет</a> для ведения воинского учета в организациях</div>
<div class="paragraph">
<div class="paragraph-left">
<div class="text icon-checklist">Кому доступен личный кабинет?</div>
<div class="short-text">Организациям, за ĸоторыми заĸреплена обязанность по осуществлению воинсĸого учета в соответствии с <a href="https://www.consultant.ru/document/cons_doc_LAW_18260/">ФЗ от 28.03.1998 No 53-ФЗ</a></div>
</div>
<div class="paragraph-right">
<div class="text icon-clock">Для чего нужен личный кабинет?</div>
<div class="short-text">Для своевременной и оперативной передачи сведений в военĸоматы в элеĸтронном виде</div>
</div>
</div>
</div>
</div>
<div class="list-group lk-access">
<div class="paragraph">
<div class="paragraph-left">
<div class="subtitle short-text">Как получить доступ к Личному кабинету?</div>
</div>
<div class="paragraph-right">
<div class="list">
<div class="esia short-text">Необходимо авторизоваться</div>
<div class="case short-text">Потребуется подтвержденная учетная запись организации</div>
<div class="user short-text">Доступ предоставляется тольĸо сотрудниĸу, наделенному соответствующими полномочиями (ролью) на ведения воинсĸого учета внутри организации</div>
</div>
<div class="btn-group">
<a href="/" class="btn">Войти в Личный кабинет</a>
</div>
</div>
</div>
</div>
<div class="list-group lk-info">
<div class="subtitle">Какие виды сведений доступны для отправки?</div>
<div class="paragraph">
<div class="paragraph-left">
<div class="section-group">
<div class="icon-case">
Сведения о приеме на работу (увольнении), зачислении в образовательную организацию (отчислении)
<div class="muted">Срок передачи сведений: <span class="detailed">до 5 дней</span></div>
</div>
<div class="icon-shield">
Изменения сведений сотрудников, необходимых для ведения воинского учета
<div class="muted">Срок передачи сведений: <span class="detailed">до 5 дней</span></div>
</div>
<div class="icon-clip">
Сообщение о гражданах, не состоящих, но обязанных состоять на воинском учете
<div class="muted">Срок передачи сведений: <span class="detailed">до 3 дней</span></div>
</div>
</div>
</div>
<div class="paragraph-right">
<div class="section-group">
<div class="icon-pers">
Ежегодное предоставление списка граждан мужского пола, подлежащих первоначальной постановке на воинский учет в год достижения ими возраста 17 лет
<div class="muted">Срок передачи сведений: <span class="detailed">ежегодно, в срок до 1 ноября</span></div>
</div>
<div class="icon-building">
Ежегодное предоставление списка сотрудников/обучающихся в организации, подлежащих воинскому учету
<div class="muted">Срок передачи сведений: <span class="detailed">ежегодно, по согласованию с военкоматом</span></div>
</div>
</div>
</div>
</div>
</div>
<div class="list-group lk-pass">
<div class="subtitle">Как передавать сведения?</div>
<div class="pass-list">
<div>Подготовьте файл-excel с данными в соответствии с форматом</div>
<div>Убедитесь, что все данные в файле введены ĸорреĸтно</div>
<div>Войдите в личный ĸабинет организации через Госуслуги</div>
<div>Выберите необходимый вид сведения и загрузите файл</div>
<div>Следите за статусом приема</div>
</div>
</div>
<div class="list-group lk-msg">
<div class="msg-list">
<span class="info"></span>Если в файле будут ошибĸи, данные не будут приняты Реестром и выгрузĸу сведений придется повторить
</div>
</div>
<div class="list-group lk-docs">
<div class="subtitle">Формы документов для заполнения</div>
<div class="docs-list">
<div><a href="#"></a>Приложение №2</div>
<div><a href="#"></a>Приложение №9</div>
<div><a href="#"></a>Приложение №10</div>
<div><a href="#"></a>Приложение №11</div>
<div><a href="#"></a>Приложение №12</div>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>ervu_lkrp_ul</title>
<title>Личный кабинет юр.лица</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>ervu_lkrp_ul</title>
<title>Личный кабинет юр.лица</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>

View file

@ -1624,9 +1624,9 @@
}
},
"@webbpm/base-package": {
"version": "3.178.2",
"resolved": "https://repo.micord.ru/repository/npm-all/@webbpm/base-package/-/base-package-3.178.2.tgz",
"integrity": "sha512-ShqAmiaGCvrg7ffrhntshwcZJVW8cK10JsMy/OT36p7iK6B/IR0YCJZZ+GIhakLo0CZsEokhcpKBdORvfOI55g==",
"version": "3.182.0",
"resolved": "https://repo.micord.ru/repository/npm-all/@webbpm/base-package/-/base-package-3.182.0.tgz",
"integrity": "sha512-8pb1hMxjiHrOPCXhWoycSJqb7LT2ldSeWnHJmS8FD53ZRJqErcYj50Nj5Yz2lcoye0OVmhFtSv/YNyRVTQX6yw==",
"requires": {
"tslib": "^1.9.0"
}

View file

@ -4,7 +4,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>ul</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.9.1-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.ul</groupId>
@ -70,6 +70,7 @@
<include>doc/**/*</include>
<include>node_modules/**/*</include>
<include>index.html</include>
<include>home.html</include>
<include>systemjs.config.js</include>
</includes>
</resource>

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Web BPM</title>
<title>Личный кабинет юр.лица</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>

View file

@ -408,15 +408,15 @@
.webbpm.ervu_lkrp_ul ag-grid-angular .grid-setting-icon,
.webbpm.ervu_lkrp_ul ag-grid-angular .ag-header-cell.ag-header-active .ag-header-cell-menu-button,
.webbpm.ervu_lkrp_ul ag-grid-angular .ag-header-cell .ag-header-icon .ag-icon,
/*.webbpm.ervu_lkrp_ul ag-grid-angular .ag-header-cell .ag-header-icon .ag-icon,*/
.webbpm.ervu_lkrp_ul ag-grid-angular .ag-header-row-column-filter :is(.ag-header-cell, .ag-header-group-cell)::after,
.webbpm.ervu_lkrp_ul ag-grid-angular .ag-filter .ag-filter-condition,
.webbpm.ervu_lkrp_ul ag-grid-angular .ag-filter .ag-filter-condition ~ * {
display: none !important;
/*display: none !important;*/
}
.webbpm.ervu_lkrp_ul ag-grid-angular .ag-floating-filter-button {
display: none !important;
/*display: none !important;*/
}
.webbpm.ervu_lkrp_ul ag-grid-angular .ag-header-row:not(:first-child) .ag-header-cell,
@ -441,7 +441,6 @@
height: 24px;
}
.webbpm.ervu_lkrp_ul ag-grid-angular .ag-icon:is(.ag-icon-small-down, .ag-icon-filter)::before {
position: absolute;
content: "";
width: 24px;
height: 24px;
@ -559,13 +558,17 @@
height: 100%;
}
.webbpm.ervu_lkrp_ul .journal grid-v2 {
.webbpm.ervu_lkrp_ul .journal in-memory-static-grid {
display: flex;
flex-direction: column;
flex: 1;
height: auto;
}
.webbpm.ervu_lkrp_ul .journal .grid {
flex-direction: column;
flex: 1 1 auto;
height: 100px;
height: 200px;
min-height: 200px;
}
.webbpm.ervu_lkrp_ul .journal .fieldset button-component {
display: block;
@ -794,11 +797,11 @@
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding-left: 60px;
padding-left: 56px;
}
.webbpm.ervu_lkrp_ul .modal.show ervu-file-upload .selected-file .selected-file-name::before {
position: absolute;
content: url(../img/svg/file-xlsx.svg);
content: url(../img/svg/file-csv.svg);
top: -2px;
left: 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

View file

@ -0,0 +1,11 @@
<svg width="42" height="24" viewBox="0 0 42 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2036_4892)">
<path d="M3.73732 24C0.852775 24 -0.941093 20.6418 0.519071 17.9752L7.65122 4.9504C8.99126 2.50318 11.4404 1 14.0877 1H38.2683C40.3292 1 42 2.79086 42 5V20C42 22.2091 40.3292 24 38.2683 24H3.73732Z" fill="#2DC36A"/>
<path d="M19.768 11.336C19.7253 11.3947 19.6827 11.44 19.64 11.472C19.5973 11.504 19.5387 11.52 19.464 11.52C19.384 11.52 19.296 11.488 19.2 11.424C19.104 11.3547 18.984 11.28 18.84 11.2C18.7013 11.12 18.528 11.048 18.32 10.984C18.1173 10.9147 17.8667 10.88 17.568 10.88C17.168 10.88 16.816 10.952 16.512 11.096C16.208 11.2347 15.952 11.4373 15.744 11.704C15.5413 11.9707 15.3867 12.2933 15.28 12.672C15.1787 13.0507 15.128 13.4747 15.128 13.944C15.128 14.4347 15.184 14.872 15.296 15.256C15.408 15.6347 15.5653 15.9547 15.768 16.216C15.976 16.472 16.224 16.6693 16.512 16.808C16.8053 16.9413 17.1333 17.008 17.496 17.008C17.8427 17.008 18.128 16.968 18.352 16.888C18.576 16.8027 18.76 16.7093 18.904 16.608C19.0533 16.5067 19.176 16.416 19.272 16.336C19.3733 16.2507 19.472 16.208 19.568 16.208C19.6853 16.208 19.776 16.2533 19.84 16.344L20.24 16.864C20.064 17.0827 19.864 17.2693 19.64 17.424C19.416 17.5787 19.1733 17.7093 18.912 17.816C18.656 17.9173 18.3867 17.992 18.104 18.04C17.8213 18.088 17.5333 18.112 17.24 18.112C16.7333 18.112 16.2613 18.0187 15.824 17.832C15.392 17.6453 15.016 17.376 14.696 17.024C14.376 16.6667 14.1253 16.2293 13.944 15.712C13.7627 15.1947 13.672 14.6053 13.672 13.944C13.672 13.3413 13.7547 12.784 13.92 12.272C14.0907 11.76 14.336 11.32 14.656 10.952C14.9813 10.5787 15.3787 10.288 15.848 10.08C16.3227 9.872 16.8667 9.768 17.48 9.768C18.0507 9.768 18.552 9.86133 18.984 10.048C19.4213 10.2293 19.808 10.488 20.144 10.824L19.768 11.336ZM26.4886 11.232C26.4246 11.3493 26.326 11.408 26.1926 11.408C26.1126 11.408 26.022 11.3787 25.9206 11.32C25.8193 11.2613 25.694 11.1973 25.5446 11.128C25.4006 11.0533 25.2273 10.9867 25.0246 10.928C24.822 10.864 24.582 10.832 24.3046 10.832C24.0646 10.832 23.8486 10.864 23.6566 10.928C23.4646 10.9867 23.2993 11.0693 23.1606 11.176C23.0273 11.2827 22.9233 11.408 22.8486 11.552C22.7793 11.6907 22.7446 11.8427 22.7446 12.008C22.7446 12.216 22.8033 12.3893 22.9206 12.528C23.0433 12.6667 23.2033 12.7867 23.4006 12.888C23.598 12.9893 23.822 13.08 24.0726 13.16C24.3233 13.2347 24.5793 13.3173 24.8406 13.408C25.1073 13.4933 25.366 13.5893 25.6166 13.696C25.8673 13.8027 26.0913 13.936 26.2886 14.096C26.486 14.256 26.6433 14.4533 26.7606 14.688C26.8833 14.9173 26.9446 15.1947 26.9446 15.52C26.9446 15.8933 26.878 16.24 26.7446 16.56C26.6113 16.8747 26.414 17.1493 26.1526 17.384C25.8913 17.6133 25.5713 17.7947 25.1926 17.928C24.814 18.0613 24.3766 18.128 23.8806 18.128C23.3153 18.128 22.8033 18.0373 22.3446 17.856C21.886 17.6693 21.4966 17.432 21.1766 17.144L21.5126 16.6C21.5553 16.5307 21.606 16.4773 21.6646 16.44C21.7233 16.4027 21.798 16.384 21.8886 16.384C21.9846 16.384 22.086 16.4213 22.1926 16.496C22.2993 16.5707 22.4273 16.6533 22.5766 16.744C22.7313 16.8347 22.918 16.9173 23.1366 16.992C23.3553 17.0667 23.6273 17.104 23.9526 17.104C24.23 17.104 24.4726 17.0693 24.6806 17C24.8886 16.9253 25.062 16.8267 25.2006 16.704C25.3393 16.5813 25.4406 16.44 25.5046 16.28C25.574 16.12 25.6086 15.9493 25.6086 15.768C25.6086 15.544 25.5473 15.36 25.4246 15.216C25.3073 15.0667 25.15 14.9413 24.9526 14.84C24.7553 14.7333 24.5286 14.6427 24.2726 14.568C24.022 14.488 23.7633 14.4053 23.4966 14.32C23.2353 14.2347 22.9766 14.1387 22.7206 14.032C22.47 13.92 22.246 13.7813 22.0486 13.616C21.8513 13.4507 21.6913 13.248 21.5686 13.008C21.4513 12.7627 21.3926 12.4667 21.3926 12.12C21.3926 11.8107 21.4566 11.5147 21.5846 11.232C21.7126 10.944 21.8993 10.6933 22.1446 10.48C22.39 10.2613 22.6913 10.088 23.0486 9.96C23.406 9.832 23.814 9.768 24.2726 9.768C24.806 9.768 25.2833 9.85333 25.7046 10.024C26.1313 10.1893 26.4993 10.4187 26.8086 10.712L26.4886 11.232ZM27.6215 9.896H28.7815C28.8988 9.896 28.9948 9.92533 29.0695 9.984C29.1442 10.0427 29.1948 10.112 29.2215 10.192L31.2775 15.408C31.3415 15.6053 31.3975 15.7973 31.4455 15.984C31.4935 16.1707 31.5388 16.3573 31.5815 16.544C31.6242 16.3573 31.6695 16.1707 31.7175 15.984C31.7655 15.7973 31.8242 15.6053 31.8935 15.408L33.9735 10.192C34.0055 10.1067 34.0588 10.0373 34.1335 9.984C34.2082 9.92533 34.2962 9.896 34.3975 9.896H35.5095L32.2055 18H30.9255L27.6215 9.896Z" fill="#FCFDF8"/>
</g>
<defs>
<clipPath id="clip0_2036_4892">
<rect width="42" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,717 @@
@font-face {
font-family: 'InterL';
src: url('fonts/Inter-Light.otf');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Inter';
src: url('fonts/Inter-Regular.otf');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'InterM';
src: url('fonts/Inter-Medium.otf');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'InterSB';
src: url('fonts/Inter-SemiBold.otf');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'InterB';
src: url('fonts/Inter-Bold.otf');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Golos';
src: url('fonts/GolosText-Regular.ttf');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'GolosM';
src: url('fonts/GolosText-Medium.ttf');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'GolosDB';
src: url('fonts/GolosText-DemiBold.ttf');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'GolosB';
src: url('fonts/GolosText-Bold.ttf');
font-weight: 400;
font-style: normal;
}
:root {
--white: #ffffff;
--black: #000000;
--color-text-primary: #1d1e21;
--color-text-secondary: #acacac;
--color-link: #c64e1b;
--color-link-hover: #fa773f;
--color-form: #3f434b;
--color-bg-main: #223d36;
--color-light: #868b87;
--bg-light: #fafafa;
--bg-warn: #ffdcc6;
--bg-form: #eee;
--border-light: #d2d2d2;
--bg-shadow: 0 19px 19px 0 rgba(230, 230, 230, 0.19), 0 1px 4px 0 #f3f3f3;
--h-header: 64px;
--w-screen: 100px;
--size-text-maintitle: 54px;
--size-text-title: 40px;
--size-text-subtitle: 32px;
--size-text-primary: 20px;
--size-text-secondary: 16px;
--indent-huge: 72px;
--indent-big: 52px;
--indent-medium: 32px;
--indent-small: 24px;
--indent-mini: 16px;
}
body, html {
height: 100%
}
body {
-ms-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
display: flex;
flex-direction: column;
padding: 0;
margin: 0;
}
a {
color: var(--color-link);
text-decoration: none;
}
a:is(:hover, :focus, :active) {
color: var(--color-link-hover);
}
button, a.btn {
display: flex;
justify-content: center;
align-items: center;
color: var(--white);
font-family: 'InterL';
font-size: var(--size-text-secondary);
padding: 0 24px;
height: 48px;
border: 1px solid transparent;
border-radius: 4px;
background: var(--color-link);
}
button:is(:hover, :focus, :active),
a.btn:is(:hover, :focus, :active) {
background: var(--color-link-hover);
cursor: pointer;
}
.btn.btn-secondary {
color: var(--color-link);
border-color: var(--color-link);
background-color: transparent;
}
.btn.btn-secondary:is(:hover, :focus, :active) {
color: var(--color-link-hover);
border-color: var(--color-link-hover);
background: transparent;
}
.header {
display: flex;
flex-direction: row;
align-items: center;
font-family: 'InterSB';
min-height: var(--h-header);
padding: 0 var(--w-screen);
border: 0;
background-color: var(--color-bg-main);
}
.header .header-logo {
width: 62px;
height: 40px;
background: url(img/svg/mil-logo.svg) no-repeat 0 50%;
}
.header .header-title {
color: var(--white);
font-size: var(--size-text-secondary);
margin-left: var(--indent-mini);
}
:is(.ul, .fl) .header {
background-color: var(--color-text-primary);
z-index: 1;
}
.container {
width: 100%;
max-width: 100%;
height: auto;
margin: 0;
padding: var(--h-header) 0 0;
position: absolute;
top: 0px;
left: 0;
right: 0;
bottom: 0;
border: 0;
overflow: hidden;
}
.container-inside {
display: flex;
flex-direction: column;
height: 100%;
color: var(--color-text-primary);
font-family: 'Inter';
padding: 0 var(--w-screen);
background-color: var(--bg-light);
overflow: auto;
}
.container-inside .short-text {
max-width: 60%;
}
.container-inside .paragraph-left .short-text {
max-width: 70%;
}
.container-inside .block-group {
display: flex;
flex-direction: row;
margin: auto;
}
.container-inside .block {
font-family: 'Inter';
min-width: 355px;
padding: 40px;
border-radius: 12px;
background-color: var(--white);
box-shadow: var(--bg-shadow);
}
.container-inside .block > div {
max-width: 350px;
}
.container-inside .block + .block {
margin-left: 40px;
}
.container-inside .block .block-img {
width: 100px;
height: 100px;
}
.container-inside .block.block-ul .block-img {
background-image: url(img/svg/ul.svg);
}
.container-inside .block.block-fl .block-img {
background-image: url(img/svg/fl.svg);
}
.container-inside .block .block-title {
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);
margin-top: var(--indent-mini);
}
.container-inside .block .btn {
width: auto;
margin-top: var(--indent-medium);
}
:is(.ul, .fl) .container-inside {
padding: 0;
background-color: var(--white);
}
:is(.ul, .fl) .container-inside .btn-group {
display: flex;
flex-direction: row;
}
:is(.ul, .fl) .container-inside .btn-group .btn + .btn {
margin-left: var(--indent-mini);
}
:is(.ul, .fl) .container-inside .list-group {
position: relative;
padding: 0 var(--w-screen);
}
:is(.ul, .fl) .container-inside .list-group .btn {
width: max-content;
}
:is(.ul, .fl) .container-inside .list-group .title {
font-size: var(--size-text-maintitle);
font-family: 'GolosB';
margin-bottom: var(--indent-huge);
}
:is(.ul, .fl) .container-inside .list-group .subtitle {
font-size: var(--size-text-title);
font-family: 'GolosDB';
margin-bottom: var(--indent-big);
}
:is(.ul, .fl) .container-inside .list-group .muted {
color: var(--color-light);
}
:is(.ul, .fl) .container-inside .list-group .paragraph {
display: flex;
flex-direction: row;
}
:is(.ul, .fl) .container-inside .list-group .paragraph .paragraph-left {
width: 40%;
}
:is(.ul, .fl) .container-inside .list-group .paragraph .paragraph-right {
width: 60%;
}
:is(.ul, .fl) .container-inside .list-group .paragraph .paragraph-half {
width: 50%;
}
:is(.ul, .fl) .container-inside .list-group .paragraph .paragraph-third {
width: 33.33%;
}
:is(.ul, .fl) .container-inside .list-group .paragraph [class*="paragraph-"] + [class*="paragraph-"] {
margin-left: 40px;
}
:is(.ul, .fl) .container-inside .list-group .paragraph .text {
font-family: 'InterSB';
font-size: var(--size-text-primary);
margin-bottom: var(--indent-mini);
}
:is(.ul, .fl) .container-inside .list-group .paragraph .icon-checklist,
:is(.ul, .fl) .container-inside .list-group .paragraph .icon-clock,
:is(.ul, .fl) .container-inside .list-group .paragraph .icon-text {
padding-top: 44px;
}
:is(.ul, .fl) .container-inside .list-group .paragraph .icon-checklist {
background: url(img/svg/checklist-32x32.svg) no-repeat 0 0;
}
:is(.ul, .fl) .container-inside .list-group .paragraph .icon-clock {
background: url(img/svg/clock-32x32.svg) no-repeat 0 0;
}
: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;
padding-left: 36px;
}
:is(.ul, .fl) .container-inside .list-group .list > div + div {
margin-top: var(--indent-mini);
}
:is(.ul, .fl) .container-inside .list-group .list > div::after {
content: "";
position: absolute;
width: 24px;
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;
}
:is(.ul, .fl) .container-inside .list-group .list > div.case::after {
background: url(img/svg/case-24x24.svg) no-repeat 0 0;
}
:is(.ul, .fl) .container-inside .list-group .list > div.user::after {
background: url(img/svg/user-24x24.svg) no-repeat 0 0;
}
:is(.ul, .fl) .container-inside .list-group .list > div.romb::after {
background: url(img/svg/romb-24x24.svg) no-repeat 0 0;
}
:is(.ul, .fl) .container-inside .list-group .list ~ .btn-group {
margin-top: var(--indent-medium);
}
:is(.ul, .fl) .container-inside .list-group .section-group > div {
display: flex;
flex-direction: column;
min-height: 48px;
position: relative;
padding: 16px 16px 16px 76px;
margin-bottom: 16px;
border-radius: 4px;
background-color: var(--bg-form);
}
:is(.ul, .fl) .container-inside .list-group .section-group > div:last-child {
margin-bottom: 0;
}
:is(.ul, .fl) .container-inside .list-group .section-group > div::before {
content: "";
position: absolute;
left: 16px;
width: 48px;
height: 48px;
border-radius: 50px;
background-color: var(--color-bg-main);
background-repeat: no-repeat;
background-position: 50% 50%;
}
:is(.ul, .fl) .container-inside .list-group .section-group > div.icon-user::before {
background-image: url(img/svg/pers-wt.svg);
}
:is(.ul, .fl) .container-inside .list-group .section-group > div.icon-case::before {
background-image: url(img/svg/case-wt.svg);
}
:is(.ul, .fl) .container-inside .list-group .section-group > div.icon-shield::before {
background-image: url(img/svg/shield-wt.svg);
}
:is(.ul, .fl) .container-inside .list-group .section-group > div.icon-clip::before {
background-image: url(img/svg/clip-wt.svg);
}
:is(.ul, .fl) .container-inside .list-group .section-group > div.icon-pers::before {
background-image: url(img/svg/pers-wt.svg);
}
:is(.ul, .fl) .container-inside .list-group .section-group > div.icon-building::before {
background-image: url(img/svg/building-wt.svg);
}
:is(.ul, .fl) .container-inside .list-group .section-group > div .muted {
margin-top: 12px;
}
:is(.ul, .fl) .container-inside .list-group .section-group > div .muted .detailed {
color: var(--color-text-primary);
font-family: 'InterB';
}
:is(.ul, .fl) .container-inside .list-group .pass-list {
position: relative;
display: flex;
flex-direction: row;
padding-top: 60px;
}
:is(.ul, .fl) .container-inside .list-group .pass-list::before {
content: "";
position: absolute;
width: calc(80% + 40px);
height: 4px;
top: 18px;
left: 0;
background-color: var(--color-link-hover);
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div {
position: relative;
width: 20%;
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div::before {
content: "";
position: absolute;
width: 32px;
height: 32px;
top: -60px;
left: 0;
border-radius: 2px;
border: 4px solid var(--color-link-hover);
background-color: var(--bg-light);
transform: rotate(45deg);
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div::after {
content: "";
position: absolute;
font-family: 'InterB';
top: -50px;
left: 15px;
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div:nth-child(1)::after {
content: "1";
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div:nth-child(2)::after {
content: "2";
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div:nth-child(3)::after {
content: "3";
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div:nth-child(4)::after {
content: "4";
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div:nth-child(5)::after {
content: "5";
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div + div {
margin-left: 40px;
}
:is(.ul, .fl) .container-inside .list-group .msg-list {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 8px;
}
:is(.ul, .fl) .container-inside .list-group .msg-list span {
width: 32px;
height: 32px;
padding-right: 16px;
background: url(img/svg/info-gr.svg) no-repeat 0 0;
}
:is(.ul, .fl) .container-inside .list-group .docs-list {
position: relative;
display: flex;
flex-direction: row;
}
:is(.ul, .fl) .container-inside .list-group .docs-list > div {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
width: 20%;
}
:is(.ul, .fl) .container-inside .list-group .docs-list > div a {
width: 24px;
height: 24px;
padding-right: 8px;
background: url(img/svg/download-24x24.svg) no-repeat 0 0;
}
:is(.ul, .fl) .container-inside .list-group .docs-list > div + div {
margin-left: 40px;
}
:is(.ul, .fl) .container-inside .list-group.lk-what {
padding-top: var(--indent-huge);
padding-bottom: var(--indent-huge);
}
:is(.ul, .fl) .container-inside .list-group.lk-what::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0.12;
background: url(img/svg/bg-star.svg) no-repeat calc(100% + 200px) -180px transparent;
z-index: 0;
}
:is(.ul, .fl) .container-inside .list-group.lk-what > div {
position: relative;
z-index: 1;
}
:is(.ul, .fl) .container-inside .list-group.lk-access {
color: var(--white);
padding-top: var(--indent-big);
padding-bottom: var(--indent-big);
background-color: var(--color-bg-main);
}
:is(.ul, .fl) .container-inside .list-group.lk-info {
padding-top: var(--indent-big);
padding-bottom: var(--indent-big);
}
:is(.ul, .fl) .container-inside .list-group.lk-pass {
padding-top: var(--indent-big);
padding-bottom: var(--indent-big);
background-color: var(--bg-light);
}
:is(.ul, .fl) .container-inside .list-group.lk-when {
color: var(--white);
padding-top: var(--indent-big);
padding-bottom: var(--indent-big);
background-color: var(--color-bg-main);
}
:is(.ul, .fl) .container-inside .list-group.lk-msg {
background-color: var(--border-light);
}
:is(.ul, .fl) .container-inside .list-group.lk-limits {
padding-top: var(--indent-big);
padding-bottom: var(--indent-big);
}
:is(.ul, .fl) .container-inside .list-group.lk-docs {
flex: 1;
color: var(--white);
padding-top: var(--indent-huge);
padding-bottom: var(--indent-huge);
background-color: var(--color-text-primary);
}
:is(.ul, .fl) .container-inside .list-group.lk-alert {
padding-top: var(--indent-big);
padding-bottom: var(--indent-big);
background-color: var(--bg-light);
}
:is(.ul, .fl) .container-inside .list-group.lk-footer {
padding-top: var(--indent-small);
padding-bottom: var(--indent-small);
background-color: var(--color-text-primary);
}
:is(.fl) .container-inside .list-group.lk-what .title {
color: var(--color-link);
margin-bottom: var(--indent-small);
}
:is(.fl) .container-inside .list-group.lk-what .title::after {
content: url(img/svg/star.svg);
top: 18px;
position: relative;
margin-left: var(--indent-big);
}
:is(.fl) .container-inside .list-group.lk-what .title + .short-text {
max-width: 25%;
}
:is(.fl) .container-inside .list-group.lk-what .title ~ .subtitle {
margin-top: var(--indent-big);
}
:is(.fl) .container-inside .list-group.lk-info .section-group > div {
justify-content: center;
}
:is(.fl) .container-inside .list-group.lk-pass .subtitle {
margin-bottom: 0;
}
:is(.fl) .container-inside .list-group.lk-pass .subtitle + div {
margin-top: var(--indent-small);
margin-bottom: var(--indent-big);
}
:is(.fl) .container-inside .list-group.lk-pass .pass-list::before {
display: none;
}
:is(.fl) .container-inside .list-group.lk-pass .pass-list > div {
position: relative;
width: 33.33%;
}
:is(.fl) .container-inside .list-group.lk-msg {
color: var(--color-link);
font-family: 'InterSB';
background-color: var(--bg-form);
}
:is(.fl) .container-inside .list-group.lk-msg span {
background: url(img/svg/info.svg) no-repeat 0 4px;
}
:is(.fl) .container-inside .list-group.lk-limits .subtitle {
margin-bottom: 0;
}
:is(.fl) .container-inside .list-group.lk-limits .subtitle + div {
margin-top: var(--indent-small);
margin-bottom: var(--indent-big);
}
:is(.fl) .container-inside .list-group.lk-limits .scheme {
width: 100%;
height: 204px;
background: url(img/svg/scheme.svg) no-repeat 0 0;
}
:is(.fl) .container-inside .list-group.lk-alert > .short-text {
margin-bottom: var(--indent-big);
}
:is(.fl) .container-inside .list-group.lk-alert .alert-block {
position: relative;
padding: var(--indent-small) 64px var(--indent-small) var(--indent-small);
border-radius: 4px;
border: 2px solid var(--border-light);
}
:is(.fl) .container-inside .list-group.lk-alert .alert-block::after {
content: url(img/svg/info.svg);
position: absolute;
top: var(--indent-small);
right: var(--indent-small);
}
:is(.fl) .container-inside .list-group.lk-alert .alert-block > div + div {
margin-top: var(--indent-small);
}
:is(.fl) .container-inside .list-group.lk-alert .alert-block > div:last-child {
color: var(--color-link);
}
/*@media ((max-width: 780px) or ((orientation: landscape) and (max-device-width : 1024px))) {*/
@media (max-width: 1024px) {
body {
--w-screen: 32px;
--size-text-maintitle: 32px;
--size-text-title: 28px;
--size-text-subtitle: 24px;
--indent-huge: 32px;
--indent-big: 24px;
--indent-medium: 24px;
--indent-small: 16px;
}
.container-inside .short-text {
max-width: 100% !important;
}
.container-inside .block-group {
flex-direction: column;
margin: 0;
}
.container-inside .block-group .block {
min-width: auto;
margin: var(--indent-huge) 0 0;
}
.container-inside .block-group .block > div {
max-width: inherit !important;
}
:is(.ul, .fl) .container-inside .list-group .paragraph {
flex-direction: column;
}
:is(.ul, .fl) .container-inside .list-group .paragraph [class*="paragraph-"] {
width: auto;
margin-left: 0;
}
:is(.ul, .fl) .container-inside .list-group .paragraph [class*="paragraph-"] + [class*="paragraph-"] {
margin-top: var(--indent-mini);
margin-left: 0;
}
:is(.ul, .fl) .container-inside .list-group .pass-list {
flex-direction: column;
padding-top: 0;
}
:is(.ul, .fl) .container-inside .list-group .pass-list::before {
display: none;
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div {
display: flex;
align-items: center;
width: auto !important;
padding-left: 60px;
min-height: 40px;
}
:is(.ul, .fl) .container-inside .list-group .pass-list > div::before {
top: 0;
}
: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);
}
}
@media (max-width: 480px) {
body {
--w-screen: 16px;
--size-text-maintitle: 28px;
--size-text-title: 24px;
--size-text-subtitle: 20px;
--indent-huge: 24px;
--indent-big: 24px;
--indent-medium: 16px;
--indent-small: 16px;
}
:is(.ul, .fl) .container-inside .list-group .docs-list {
flex-direction: column;
}
:is(.ul, .fl) .container-inside .list-group .docs-list > div {
width: 100%;
}
:is(.ul, .fl) .container-inside .list-group .docs-list > div + div {
margin-left: 0;
margin-top: var(--indent-mini);
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.8 MiB

View file

@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.26814 12.0027H4.24414C3.41571 12.0027 2.74414 12.6743 2.74414 13.5027L2.74414 21.2559H8.26814" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
<path d="M8.26758 4.25158L8.26758 21.2549H21.2676V4.25158C21.2676 3.42315 20.596 2.75158 19.7676 2.75158H9.76758C8.93915 2.75158 8.26758 3.42315 8.26758 4.25158Z" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
<path d="M12.3672 9.55371V8.05371" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
<path d="M17.168 8.05371V9.55371" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
<path d="M12.3672 15.9541V14.4541" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
<path d="M17.168 14.4541V15.9541" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 867 B

View file

@ -0,0 +1,3 @@
<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="M10.7158 3C9.35852 3 8.20421 3.9903 7.99782 5.33184L7.89503 6H4C2.89543 6 2 6.89543 2 8V18C2 19.6569 3.34315 21 5 21H19C20.6569 21 22 19.6569 22 18V8C22 6.89543 21.1046 6 20 6H16.105L16.0022 5.33184C15.7958 3.99031 14.6415 3 13.2842 3H10.7158ZM14.5873 6L14.5196 5.55993C14.4258 4.95014 13.9011 4.5 13.2842 4.5H10.7158C10.0989 4.5 9.57419 4.95014 9.48038 5.55993L9.41268 6H14.5873ZM4 7.5H20C20.2761 7.5 20.5 7.72386 20.5 8V10.9297C18.8456 11.9818 16.9613 12.684 15 13.0362V13C15 11.8954 14.1046 11 13 11H11C9.89543 11 9 11.8954 9 13V13.0362C7.03871 12.684 5.1544 11.9818 3.5 10.9297V8C3.5 7.72386 3.72386 7.5 4 7.5ZM9 14.5582C7.06675 14.2404 5.19401 13.6122 3.5 12.6736V18C3.5 18.8284 4.17157 19.5 5 19.5H19C19.8284 19.5 20.5 18.8284 20.5 18V12.6736C18.806 13.6122 16.9332 14.2404 15 14.5582V15C15 16.1046 14.1046 17 13 17H11C9.89543 17 9 16.1046 9 15V14.5582ZM11 12.5H13C13.2761 12.5 13.5 12.7239 13.5 13V15C13.5 15.2761 13.2761 15.5 13 15.5H11C10.7239 15.5 10.5 15.2761 10.5 15V13C10.5 12.7239 10.7239 12.5 11 12.5Z" fill="#FA773F"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,3 @@
<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="M10.7158 3C9.35852 3 8.20421 3.9903 7.99782 5.33184L7.89503 6H4C2.89543 6 2 6.89543 2 8V18C2 19.6569 3.34315 21 5 21H19C20.6569 21 22 19.6569 22 18V8C22 6.89543 21.1046 6 20 6H16.105L16.0022 5.33184C15.7958 3.99031 14.6415 3 13.2842 3H10.7158ZM14.5873 6L14.5196 5.55993C14.4258 4.95014 13.9011 4.5 13.2842 4.5H10.7158C10.0989 4.5 9.57419 4.95014 9.48038 5.55993L9.41268 6H14.5873ZM4 7.5H20C20.2761 7.5 20.5 7.72386 20.5 8V10.9297C18.8456 11.9818 16.9613 12.684 15 13.0362V13C15 11.8954 14.1046 11 13 11H11C9.89543 11 9 11.8954 9 13V13.0362C7.03871 12.684 5.1544 11.9818 3.5 10.9297V8C3.5 7.72386 3.72386 7.5 4 7.5ZM9 14.5582C7.06675 14.2404 5.19401 13.6122 3.5 12.6736V18C3.5 18.8284 4.17157 19.5 5 19.5H19C19.8284 19.5 20.5 18.8284 20.5 18V12.6736C18.806 13.6122 16.9332 14.2404 15 14.5582V15C15 16.1046 14.1046 17 13 17H11C9.89543 17 9 16.1046 9 15V14.5582ZM11 12.5H13C13.2761 12.5 13.5 12.7239 13.5 13V15C13.5 15.2761 13.2761 15.5 13 15.5H11C10.7239 15.5 10.5 15.2761 10.5 15V13C10.5 12.7239 10.7239 12.5 11 12.5Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 8C4 5.79086 5.79086 4 8 4H24C26.2091 4 28 5.79086 28 8V24C28 26.2091 26.2091 28 24 28H8C5.79086 28 4 26.2091 4 24V8Z" fill="#C64E1B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.0103 17.6088L10.57 12.1602L9.50989 13.2213L15.4802 19.2008C15.6207 19.3416 15.8114 19.4207 16.0102 19.4207C16.209 19.4207 16.3996 19.3417 16.5402 19.2009L30.4835 5.24004L29.4235 4.17871L16.0103 17.6088Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 522 B

View file

@ -0,0 +1,7 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.4938 17.7404L18.4938 12.3538C18.4938 11.636 17.9119 11.0541 17.1941 11.0542C16.4783 11.0542 15.8973 11.633 15.8945 12.3488L15.8757 18.0897C15.8701 19.5264 17.0283 20.6967 18.465 20.706C19.911 20.7154 21.0882 19.5458 21.0882 18.0998L21.0882 13.935" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
<path d="M18.4766 7.7563V2.46671C18.4766 1.91442 18.0288 1.46671 17.4766 1.46671H5.35156C4.79928 1.46671 4.35156 1.91443 4.35156 2.46671V18.0459H11.4141" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.96094 11.335L8.96094 13.3369" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.8672 5.21777L13.8672 7.21973" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.96094 5.21777L8.96094 7.21973" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.9993" cy="15.9993" r="13.3333" fill="#C64E1B"/>
<path d="M16 9.375V15.8319C16 16.3666 16.2141 16.879 16.5945 17.2548L19.3333 19.9601" stroke="white" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 288 B

View file

@ -0,0 +1,3 @@
<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="M7 5.5H11V4H7C5.34315 4 4 5.34315 4 7V17C4 18.6569 5.34315 20 7 20H17C18.6569 20 20 18.6569 20 17V13H18.5V17C18.5 17.8284 17.8284 18.5 17 18.5H7C6.17157 18.5 5.5 17.8284 5.5 17V7C5.5 6.17157 6.17157 5.5 7 5.5ZM15 5.5L17.4394 5.5L8.96967 13.9697L10.0303 15.0303L18.5001 6.56066V9.00001H20.0001V4.75C20.0001 4.33579 19.6643 4.00001 19.2501 4L15 4L15 5.5Z" fill="#FA773F"/>
</svg>

After

Width:  |  Height:  |  Size: 523 B

View file

@ -0,0 +1,4 @@
<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="M3.44531 9.5C3.0311 9.5 2.69531 9.83579 2.69531 10.25C2.69531 10.6642 3.0311 11 3.44531 11H9.83731C10.2515 11 10.5873 10.6642 10.5873 10.25C10.5873 9.83579 10.2515 9.5 9.83731 9.5H3.44531ZM3.44531 13C3.0311 13 2.69531 13.3358 2.69531 13.75C2.69531 14.1642 3.0311 14.5 3.44531 14.5H15.4853C15.8995 14.5 16.2353 14.1642 16.2353 13.75C16.2353 13.3358 15.8995 13 15.4853 13H3.44531Z" fill="#FA773F"/>
<path d="M3.72852 16.9899C4.03952 17.3879 4.42752 17.7289 4.87852 17.9889L9.99952 20.9489C11.2375 21.6639 12.7615 21.6639 13.9995 20.9489L18.7495 18.2069C19.9875 17.4919 20.7495 16.1719 20.7495 14.7429V9.25788C20.7495 7.82888 19.9875 6.50788 18.7495 5.79388L13.9995 3.05188C12.7615 2.33688 11.2375 2.33688 9.99952 3.05188L4.87852 6.01188C4.42752 6.27288 4.03952 6.61387 3.72852 7.01088" stroke="#FA773F" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1,021 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 3.6 MiB

View file

@ -0,0 +1,4 @@
<svg width="33" height="33" viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle opacity="0.1" cx="16.5" cy="16.9495" r="12.8333" fill="black" stroke="black" stroke-linejoin="round"/>
<path d="M15.212 13.0138H17.788V10.5498H15.212V13.0138ZM15.324 14.9498V23.6165H17.676V14.9498H15.324Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 332 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 464 KiB

View file

@ -0,0 +1,3 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.3627 3.88944C16.6803 3.88466 18.0641 4.55448 18.894 5.83335C19.7349 7.12922 19.9475 8.94322 19.152 11.1454C19.4509 11.3622 19.6741 11.6343 19.8331 11.9118C20.0961 12.3708 20.2005 12.87 20.2005 13.2484C20.2005 14.3912 19.466 15.705 18.2185 16.2708C16.4506 22.592 7.17108 22.7528 5.53967 16.1546C4.44756 15.5333 3.81445 14.3157 3.81445 13.2494C3.81445 12.7381 4.02849 11.8265 4.74947 11.237C4.48716 10.4063 4.35645 9.60421 4.35645 8.84336C4.35645 5.52209 6.47963 3.54106 8.89442 2.80599C11.1168 2.12949 13.7657 2.45649 15.3627 3.88944ZM16.8357 15.6264C16.8452 15.5408 16.8693 15.4581 16.9063 15.3818C17.0148 15.14 17.2437 14.975 17.5029 14.9445C18.2327 14.6655 18.7005 13.8776 18.7005 13.2484C18.7005 13.1137 18.6558 12.8744 18.5315 12.6574C18.4159 12.4555 18.2495 12.3002 17.9999 12.2313C17.7901 12.1734 17.6158 12.0272 17.5222 11.8307C17.4287 11.6341 17.4252 11.4066 17.5125 11.2073C18.4656 9.03295 18.2139 7.54095 17.6357 6.64987C17.04 5.73184 16.0199 5.31359 15.149 5.40064C14.9183 5.42371 14.6898 5.33872 14.5303 5.17045C13.4808 4.06385 11.3178 3.63628 9.33123 4.24098C7.41377 4.82466 5.85645 6.31263 5.85645 8.84336C5.85645 9.57304 6.01197 10.3955 6.35928 11.2966C6.50813 11.6829 6.31595 12.1167 5.92987 12.2659C5.71638 12.3484 5.56663 12.5029 5.46252 12.6997C5.35272 12.9073 5.31445 13.1263 5.31445 13.2494C5.31445 13.8568 5.74932 14.6306 6.44148 14.9307C6.6208 14.9938 6.77268 15.1238 6.86109 15.2967C6.89847 15.3693 6.92397 15.4477 6.93624 15.5289C8.01723 20.8672 15.5973 20.9003 16.8357 15.6264ZM16.0663 9.13749C16.3009 8.79606 16.2142 8.32916 15.8728 8.09464C15.5314 7.86011 15.0645 7.94677 14.8299 8.28819C13.4432 10.307 10.6719 11.1742 8.36184 10.3842C7.96991 10.2502 7.54353 10.4592 7.40949 10.8511C7.27546 11.2431 7.48452 11.6694 7.87645 11.8035C10.8004 12.8035 14.285 11.7307 16.0663 9.13749Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,3 @@
<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="M18.5 8.83007H18.5L18.5 8.83999L18.5003 8.92876C18.5003 9.96687 18.3814 10.964 18.1609 11.9049L5.5 7.38315V4.75529C7.4809 5.16366 9.6853 5.38884 11.9996 5.38884C14.3142 5.38884 16.5189 5.16359 18.5 4.75511V8.83007ZM5.50008 8.97597C5.50178 9.46487 5.52987 9.9446 5.58252 10.4136L17.1884 14.5917C17.39 14.1925 17.5713 13.776 17.7301 13.3439L5.50008 8.97597ZM16.4097 15.9056L5.89162 12.1191C6.79191 15.6777 9.15187 18.4007 12.0002 19.4431C13.719 18.814 15.26 17.573 16.4097 15.9056ZM4.64183 3.01773C4.31938 2.93469 4 3.17475 4 3.50772V8.92791V8.92876C4 14.6869 7.32873 19.5349 11.8562 20.9789C11.9498 21.0088 12.0505 21.0088 12.1441 20.9789C16.6716 19.5349 20.0003 14.6869 20.0003 8.92876C20.0003 8.89583 20.0002 8.86294 20 8.83007V3.5075C20 3.17452 19.6806 2.93446 19.3581 3.01752C17.2113 3.57052 14.6929 3.88884 11.9996 3.88884C9.30662 3.88884 6.78849 3.5706 4.64183 3.01773Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 3.6 MiB

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.76172 20.9419C4.76172 18.9559 5.58268 17.1559 6.90161 15.8529C7.56958 15.1929 8.36654 14.6609 9.24849 14.2969L11.9974 16.4647L14.7462 14.2969C15.6292 14.6609 16.4251 15.1929 17.0931 15.8529C18.412 17.1559 19.233 18.9559 19.233 20.9419" stroke="#FA773F" stroke-width="1.49996" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.2375 6.87359C15.2375 9.33359 13.6176 10.5636 11.9976 10.5636C10.3777 10.5636 8.75781 9.33359 8.75781 6.87359C8.75781 4.41359 10.3777 3.18359 11.9976 3.18359C13.6176 3.18359 15.2375 4.42359 15.2375 6.87359Z" stroke="#FA773F" stroke-width="1.49996" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View file

@ -1,6 +1,7 @@
<button class="user-info" ngbDropdownToggle *ngIf="getIsAuth()">{{getUserFullname()}}</button>
<div ngbDropdownMenu *ngIf="getIsAuth()">
<button class="user-info" ngbDropdownToggle *ngIf="isAuthenticated()">{{getUserFullname()}}</button>
<div ngbDropdownMenu *ngIf="isAuthenticated()">
<div class="user-department">{{getOrgUnitName()}}</div>
<a routerLink="/mydata" class="data">Данные организации</a>
<button ngbDropdownItem class="exit" (click)="logout()">Выйти</button>
</div>
</div>
<button class="exit" *ngIf="!isAuthenticated()" (click)="logout()">Выйти</button>

View file

@ -0,0 +1,9 @@
<div>
<button type="button"
[disabled]="!isEnabled()"
class="btn btn-secondary"
[ngbTooltip]="tooltip | emptyIfNull"
(click)="onClick()"
(focus)="onFocus()"
(blur)="onBlur()">{{caption}}</button>
</div>

View file

@ -0,0 +1,83 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from "@angular/core";
import {AbstractButton, MessagesService, NotNull, ObjectRef} from "@webbpm/base-package";
import {HttpClient} from "@angular/common/http";
import {InMemoryStaticGrid} from "../grid/InMemoryStaticGrid";
import {Subscription} from "rxjs";
/**
* @author: Eduard Tihomirov
*/
@Component({
moduleId: module.id,
selector: 'ervu-download-file-button',
templateUrl: "./../../../../../src/resources/template/ervu/component/button/Button.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ErvuDownloadFileButton extends AbstractButton {
private httpClient: HttpClient;
private messageService: MessagesService;
private gridLoadedSubscription: Subscription;
@ObjectRef()
@NotNull()
public grid: InMemoryStaticGrid;
@NotNull()
public fileName: string;
@NotNull()
public noFileMessage: string;
constructor(el: ElementRef, cd: ChangeDetectorRef) {
super(el, cd);
}
initialize() {
super.initialize();
this.httpClient = this.injector.get(HttpClient);
this.messageService = this.injector.get(MessagesService);
if (this.grid.isInitialized() && this.grid.getRowDataSize() > 0) {
this.setEnabled(true);
this.setVisible(true);
}
this.gridLoadedSubscription = this.grid.gridLoaded.subscribe(() => {
if (this.grid.getRowDataSize() > 0) {
this.setEnabled(true);
this.setVisible(true);
}
else {
this.setEnabled(false);
this.setVisible(false);
}
});
}
ngOnDestroy() {
super.ngOnDestroy();
this.gridLoadedSubscription.unsubscribe();
}
public doClickActions(): Promise<any> {
return this.httpClient.get('kafka/excerpt', {
responseType: 'blob',
observe: 'response'
}).toPromise().then((response) => {
if (response.status == 204) {
this.messageService.info(this.noFileMessage)
}
const contentDisposition = response.headers.get('Content-Disposition');
const url = window.URL.createObjectURL(response.body);
const a = document.createElement('a');
const fileNameMatch = contentDisposition.match(/filename\*=?UTF-8''(.+)/i);
if (fileNameMatch && fileNameMatch.length > 1) {
this.fileName = decodeURIComponent(fileNameMatch[1].replace(/\+/g, '%20'));
}
a.href = url;
a.download = this.fileName;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
a.remove();
});
}
}

View file

@ -4,12 +4,15 @@ import {
Visible,
Event,
MessagesService,
UnsupportedOperationError
UnsupportedOperationError,
AppConfigService
} from "@webbpm/base-package";
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from "@angular/core";
import {FileItem, FileUploader} from "ng2-file-upload";
import {FileLikeObject} from "ng2-file-upload/file-upload/file-like-object.class";
import {EmployeeInfoFileFormType} from "./EmployeeInfoFileFormType";
import {CookieService} from "ngx-cookie";
import {TokenConstants} from "../../../modules/security/TokenConstants";
@Component({
moduleId: module.id,
@ -18,6 +21,8 @@ import {EmployeeInfoFileFormType} from "./EmployeeInfoFileFormType";
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ErvuFileUpload extends InputControl {
private static readonly BACKEND_URL: string = "backend.context";
@NotNull("true")
public selectFileFieldText: string;
@NotNull("true")
@ -46,17 +51,17 @@ export class ErvuFileUpload extends InputControl {
public fileUploadEndEvent: Event<any> = new Event<any>();
@Visible("false")
public fileUploadFailedEvent: Event<any> = new Event<any>();
@Visible("false")
public isDropZoneVisible: boolean = true;
@Visible("false")
public isFilesListVisible: boolean = true;
@Visible("false")
public isProgressBarVisible: boolean = false;
private fileInputEl: HTMLInputElement;
private url: string = '/backend/employee/document';
private messagesService: MessagesService;
private isUploadErrorOccurred = false;
private appConfigService: AppConfigService;
private cookieService: CookieService;
constructor(el: ElementRef, cd: ChangeDetectorRef) {
super(el, cd);
@ -66,6 +71,9 @@ export class ErvuFileUpload extends InputControl {
initialize() {
super.initialize();
this.messagesService = this.injector.get(MessagesService);
this.appConfigService = this.injector.get(AppConfigService);
this.cookieService = this.injector.get(CookieService);
this.url = `/${this.appConfigService.getParamValue(ErvuFileUpload.BACKEND_URL)}/employee/document`;
this.uploader.setOptions({
url: this.url,
@ -87,10 +95,20 @@ export class ErvuFileUpload extends InputControl {
}],
maxFileSize: this.maxFileSizeMb ? this.maxFileSizeMb * 1024 * 1024 : undefined,
queueLimit: this.maxFilesToUpload ? this.maxFilesToUpload : undefined,
headers: [{
name: "X-Employee-Info-File-Form-Type",
value: EmployeeInfoFileFormType[this.formType]
}]
headers: [
{
name: "X-Employee-Info-File-Form-Type",
value: EmployeeInfoFileFormType[this.formType]
},
{
name: "Client-Time-Zone",
value: Intl.DateTimeFormat().resolvedOptions().timeZone
},
{
name: TokenConstants.CSRF_HEADER_NAME,
value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME)
}
]
});
this.setUploaderMethods();
@ -119,6 +137,24 @@ export class ErvuFileUpload extends InputControl {
private setUploaderMethods() {
this.uploader.onBeforeUploadItem = (fileItem: FileItem) => {
//refresh headers
this.uploader.setOptions({
headers: [
{
name: "X-Employee-Info-File-Form-Type",
value: EmployeeInfoFileFormType[this.formType]
},
{
name: "Client-Time-Zone",
value: Intl.DateTimeFormat().resolvedOptions().timeZone
},
{
name: TokenConstants.CSRF_HEADER_NAME,
value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME)
}
]
});
this.fileUploadStartEvent.trigger();
this.isDropZoneVisible = false;
this.isFilesListVisible = false;
@ -229,4 +265,4 @@ export class ErvuFileUpload extends InputControl {
this.isUploadErrorOccurred = false;
this.cd.markForCheck();
}
}
}

View file

@ -27,7 +27,6 @@ export class StaticColumnInitializer {
columnDef.resizable = !column.widthFixed;
columnDef.headerComponentParams = {"disable_hiding": column.disableHiding || false};
columnDef.lockVisible = column.disableHiding;
columnDef.headerComponent = GridSettingHeader;
columnDef.headerTooltip = column.headerTooltip ? column.headerTooltip : column.displayName;
columnDef.suppressMenu = column.suppressHeaderMenu;
@ -52,7 +51,7 @@ export class StaticColumnInitializer {
if (type != null) {
if (gridRef.floatingFilter && column.filter !== false) {
if (column.filter !== false) {
columnDef.floatingFilter = gridRef.floatingFilter;
columnDef.filter = GridColumnFilterUtils.columnFilter(type);

View file

@ -0,0 +1,38 @@
import {DateTimeUtil, DefaultValueFormatter, GridValueFormatter} from "@webbpm/base-package";
import {ValueFormatterParams} from "ag-grid-community";
export class ClientDateTimeFormatter extends DefaultValueFormatter implements GridValueFormatter {
public dateFormat: string = '';
format(params: ValueFormatterParams): string {
if (this.isValueEmpty(params)) {
return super.format(params);
}
// don't apply formatter to row with aggregation function
if (params.node.isRowPinned()) {
return params.value;
}
if (!this.dateFormat) {
return ClientDateTimeFormatter.parseForClientTimeZoneAndFormat(params.value, DateTimeUtil.TIMESTAMP_FORMAT);
}
if (!ClientDateTimeFormatter.isValidFormat(this.dateFormat)) {
throw new Error('Invalid date format = ' + this.dateFormat);
}
return ClientDateTimeFormatter.parseForClientTimeZoneAndFormat(params.value, this.dateFormat);
}
private static isValidFormat(format: string): boolean {
const validCharsRegex = /^[YyMmDdHhSsTZ.:\[\] -]*$/;
return format && validCharsRegex.test(format);
}
private static parseForClientTimeZoneAndFormat(value: string, dateFormat: string): string {
let timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
return DateTimeUtil.parseIsoDateTime(value).tz(timezone).format(dateFormat);
}
}

View file

@ -2,10 +2,10 @@ import {AnalyticalScope, Behavior, Container, ControlWithValue} from "@webbpm/ba
import {HttpClient} from "@angular/common/http";
import {OrgData} from "./OrgData";
import {OrgInfoModel} from "../generated/ru/micord/ervu/security/esia/model/OrgInfoModel";
import {CookieService} from "ngx-cookie";
import {AuthenticationService} from "../modules/security/authentication.service";
@AnalyticalScope(Container)
export class OrgDataRoot extends Behavior{
export class OrgDataRoot extends Behavior {
private container: Container;
@ -14,8 +14,9 @@ export class OrgDataRoot extends Behavior{
this.container = this.getScript(Container);
let orgScripts: OrgData[] = this.container.getScriptsInThisAndChildren(OrgData);
let httpClient = this.injector.get(HttpClient);
let cookieService = this.injector.get(CookieService);
if (cookieService.get("webbpm.ervu-lkrp-ul")) {
let cookieService = this.injector.get(AuthenticationService);
if (cookieService.isAuthenticated()) {
httpClient.get<OrgInfoModel>("esia/org")
.toPromise()
.then(orgInfoModel => {
@ -23,9 +24,9 @@ export class OrgDataRoot extends Behavior{
return;
}
for (let orgData of orgScripts) {
let control: ControlWithValue = orgData.getScriptInObject(orgData.getObjectId(),
'component.ControlWithValue');
control.setValue(orgInfoModel[orgData.dataId]);
let control: ControlWithValue = orgData.getScriptInObject(orgData.getObjectId(),
'component.ControlWithValue');
control.setValue(orgInfoModel[orgData.dataId]);
}
});
}

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