Merge remote-tracking branch 'origin/develop' into feature/SUPPORT-9509_fix_mchd

This commit is contained in:
Eduard Tihomiorv 2025-10-28 10:43:40 +03:00
commit 56a682bdeb
19 changed files with 1200 additions and 17 deletions

View file

@ -6,9 +6,11 @@ package ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.Databasechangeloglock;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.OkopfRecords;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.OrganizationAllowed;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.Shedlock;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.records.DatabasechangeloglockRecord;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.records.OkopfRecordsRecord;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.records.OrganizationAllowedRecord;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.records.ShedlockRecord;
import org.jooq.TableField;
@ -30,5 +32,7 @@ public class Keys {
public static final UniqueKey<DatabasechangeloglockRecord> DATABASECHANGELOGLOCK_PKEY = Internal.createUniqueKey(Databasechangeloglock.DATABASECHANGELOGLOCK, DSL.name("databasechangeloglock_pkey"), new TableField[] { Databasechangeloglock.DATABASECHANGELOGLOCK.ID }, true);
public static final UniqueKey<OkopfRecordsRecord> OKOPF_RECORDS_PKEY = Internal.createUniqueKey(OkopfRecords.OKOPF_RECORDS, DSL.name("okopf_records_pkey"), new TableField[] { OkopfRecords.OKOPF_RECORDS.OKOPF_RECORDS_ID }, true);
public static final UniqueKey<OrganizationAllowedRecord> PK_ORGANIZATION_ALLOWED = Internal.createUniqueKey(OrganizationAllowed.ORGANIZATION_ALLOWED, DSL.name("pk_organization_allowed"), new TableField[] { OrganizationAllowed.ORGANIZATION_ALLOWED.ID }, true);
public static final UniqueKey<OrganizationAllowedRecord> UNI_ORGANIZATION_ALLOWED_OGRN = Internal.createUniqueKey(OrganizationAllowed.ORGANIZATION_ALLOWED, DSL.name("uni_organization_allowed_ogrn"), new TableField[] { OrganizationAllowed.ORGANIZATION_ALLOWED.OGRN }, true);
public static final UniqueKey<ShedlockRecord> SHEDLOCK_PK = Internal.createUniqueKey(Shedlock.SHEDLOCK, DSL.name("shedlock_pk"), new TableField[] { Shedlock.SHEDLOCK.NAME }, true);
}

View file

@ -9,6 +9,7 @@ import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.Databasechangelog;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.Databasechangeloglock;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.InteractionLog;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.OkopfRecords;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.OrganizationAllowed;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.Shedlock;
import java.util.Arrays;
@ -52,6 +53,11 @@ public class Public extends SchemaImpl {
*/
public final OkopfRecords OKOPF_RECORDS = OkopfRecords.OKOPF_RECORDS;
/**
* The table <code>public.organization_allowed</code>.
*/
public final OrganizationAllowed ORGANIZATION_ALLOWED = OrganizationAllowed.ORGANIZATION_ALLOWED;
/**
* The table <code>public.shedlock</code>.
*/
@ -77,6 +83,7 @@ public class Public extends SchemaImpl {
Databasechangeloglock.DATABASECHANGELOGLOCK,
InteractionLog.INTERACTION_LOG,
OkopfRecords.OKOPF_RECORDS,
OrganizationAllowed.ORGANIZATION_ALLOWED,
Shedlock.SHEDLOCK
);
}

View file

@ -8,6 +8,7 @@ import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.Databasechangelog;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.Databasechangeloglock;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.InteractionLog;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.OkopfRecords;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.OrganizationAllowed;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.Shedlock;
@ -37,6 +38,11 @@ public class Tables {
*/
public static final OkopfRecords OKOPF_RECORDS = OkopfRecords.OKOPF_RECORDS;
/**
* The table <code>public.organization_allowed</code>.
*/
public static final OrganizationAllowed ORGANIZATION_ALLOWED = OrganizationAllowed.ORGANIZATION_ALLOWED;
/**
* The table <code>public.shedlock</code>.
*/

View file

@ -0,0 +1,238 @@
/*
* This file is generated by jOOQ.
*/
package ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.Keys;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.Public;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.records.OrganizationAllowedRecord;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.jooq.Condition;
import org.jooq.Field;
import org.jooq.Identity;
import org.jooq.Name;
import org.jooq.PlainSQL;
import org.jooq.QueryPart;
import org.jooq.SQL;
import org.jooq.Schema;
import org.jooq.Select;
import org.jooq.Stringly;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.TableOptions;
import org.jooq.UniqueKey;
import org.jooq.impl.DSL;
import org.jooq.impl.SQLDataType;
import org.jooq.impl.TableImpl;
/**
* This class is generated by jOOQ.
*/
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class OrganizationAllowed extends TableImpl<OrganizationAllowedRecord> {
private static final long serialVersionUID = 1L;
/**
* The reference instance of <code>public.organization_allowed</code>
*/
public static final OrganizationAllowed ORGANIZATION_ALLOWED = new OrganizationAllowed();
/**
* The class holding records for this type
*/
@Override
public Class<OrganizationAllowedRecord> getRecordType() {
return OrganizationAllowedRecord.class;
}
/**
* The column <code>public.organization_allowed.id</code>.
*/
public final TableField<OrganizationAllowedRecord, Long> ID = createField(DSL.name("id"), SQLDataType.BIGINT.nullable(false).identity(true), this, "");
/**
* The column <code>public.organization_allowed.ogrn</code>.
*/
public final TableField<OrganizationAllowedRecord, String> OGRN = createField(DSL.name("ogrn"), SQLDataType.VARCHAR(15).nullable(false), this, "");
private OrganizationAllowed(Name alias, Table<OrganizationAllowedRecord> aliased) {
this(alias, aliased, (Field<?>[]) null, null);
}
private OrganizationAllowed(Name alias, Table<OrganizationAllowedRecord> aliased, Field<?>[] parameters, Condition where) {
super(alias, null, aliased, parameters, DSL.comment(""), TableOptions.table(), where);
}
/**
* Create an aliased <code>public.organization_allowed</code> table
* reference
*/
public OrganizationAllowed(String alias) {
this(DSL.name(alias), ORGANIZATION_ALLOWED);
}
/**
* Create an aliased <code>public.organization_allowed</code> table
* reference
*/
public OrganizationAllowed(Name alias) {
this(alias, ORGANIZATION_ALLOWED);
}
/**
* Create a <code>public.organization_allowed</code> table reference
*/
public OrganizationAllowed() {
this(DSL.name("organization_allowed"), null);
}
@Override
public Schema getSchema() {
return aliased() ? null : Public.PUBLIC;
}
@Override
public Identity<OrganizationAllowedRecord, Long> getIdentity() {
return (Identity<OrganizationAllowedRecord, Long>) super.getIdentity();
}
@Override
public UniqueKey<OrganizationAllowedRecord> getPrimaryKey() {
return Keys.PK_ORGANIZATION_ALLOWED;
}
@Override
public List<UniqueKey<OrganizationAllowedRecord>> getUniqueKeys() {
return Arrays.asList(Keys.UNI_ORGANIZATION_ALLOWED_OGRN);
}
@Override
public OrganizationAllowed as(String alias) {
return new OrganizationAllowed(DSL.name(alias), this);
}
@Override
public OrganizationAllowed as(Name alias) {
return new OrganizationAllowed(alias, this);
}
@Override
public OrganizationAllowed as(Table<?> alias) {
return new OrganizationAllowed(alias.getQualifiedName(), this);
}
/**
* Rename this table
*/
@Override
public OrganizationAllowed rename(String name) {
return new OrganizationAllowed(DSL.name(name), null);
}
/**
* Rename this table
*/
@Override
public OrganizationAllowed rename(Name name) {
return new OrganizationAllowed(name, null);
}
/**
* Rename this table
*/
@Override
public OrganizationAllowed rename(Table<?> name) {
return new OrganizationAllowed(name.getQualifiedName(), null);
}
/**
* Create an inline derived table from this table
*/
@Override
public OrganizationAllowed where(Condition condition) {
return new OrganizationAllowed(getQualifiedName(), aliased() ? this : null, null, condition);
}
/**
* Create an inline derived table from this table
*/
@Override
public OrganizationAllowed where(Collection<? extends Condition> conditions) {
return where(DSL.and(conditions));
}
/**
* Create an inline derived table from this table
*/
@Override
public OrganizationAllowed where(Condition... conditions) {
return where(DSL.and(conditions));
}
/**
* Create an inline derived table from this table
*/
@Override
public OrganizationAllowed where(Field<Boolean> condition) {
return where(DSL.condition(condition));
}
/**
* Create an inline derived table from this table
*/
@Override
@PlainSQL
public OrganizationAllowed where(SQL condition) {
return where(DSL.condition(condition));
}
/**
* Create an inline derived table from this table
*/
@Override
@PlainSQL
public OrganizationAllowed where(@Stringly.SQL String condition) {
return where(DSL.condition(condition));
}
/**
* Create an inline derived table from this table
*/
@Override
@PlainSQL
public OrganizationAllowed where(@Stringly.SQL String condition, Object... binds) {
return where(DSL.condition(condition, binds));
}
/**
* Create an inline derived table from this table
*/
@Override
@PlainSQL
public OrganizationAllowed where(@Stringly.SQL String condition, QueryPart... parts) {
return where(DSL.condition(condition, parts));
}
/**
* Create an inline derived table from this table
*/
@Override
public OrganizationAllowed whereExists(Select<?> select) {
return where(DSL.exists(select));
}
/**
* Create an inline derived table from this table
*/
@Override
public OrganizationAllowed whereNotExists(Select<?> select) {
return where(DSL.notExists(select));
}
}

View file

@ -0,0 +1,79 @@
/*
* This file is generated by jOOQ.
*/
package ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.records;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.OrganizationAllowed;
import org.jooq.Record1;
import org.jooq.impl.UpdatableRecordImpl;
/**
* This class is generated by jOOQ.
*/
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class OrganizationAllowedRecord extends UpdatableRecordImpl<OrganizationAllowedRecord> {
private static final long serialVersionUID = 1L;
/**
* Setter for <code>public.organization_allowed.id</code>.
*/
public void setId(Long value) {
set(0, value);
}
/**
* Getter for <code>public.organization_allowed.id</code>.
*/
public Long getId() {
return (Long) get(0);
}
/**
* Setter for <code>public.organization_allowed.ogrn</code>.
*/
public void setOgrn(String value) {
set(1, value);
}
/**
* Getter for <code>public.organization_allowed.ogrn</code>.
*/
public String getOgrn() {
return (String) get(1);
}
// -------------------------------------------------------------------------
// Primary key information
// -------------------------------------------------------------------------
@Override
public Record1<Long> key() {
return (Record1) super.key();
}
// -------------------------------------------------------------------------
// Constructors
// -------------------------------------------------------------------------
/**
* Create a detached OrganizationAllowedRecord
*/
public OrganizationAllowedRecord() {
super(OrganizationAllowed.ORGANIZATION_ALLOWED);
}
/**
* Create a detached, initialised OrganizationAllowedRecord
*/
public OrganizationAllowedRecord(Long id, String ogrn) {
super(OrganizationAllowed.ORGANIZATION_ALLOWED);
setId(id);
setOgrn(ogrn);
resetChangedOnNotNull();
}
}

View file

@ -0,0 +1,25 @@
package ru.micord.ervu.dao;
import ervu_lkrp_ul.ervu_lkrp_ul.db_beans.public_.tables.OrganizationAllowed;
import org.jooq.DSLContext;
import org.springframework.stereotype.Repository;
/**
* @author Adel Kalimullin
*/
@Repository
public class OrganizationAccessDao {
private final DSLContext dsl;
public OrganizationAccessDao(DSLContext dsl) {
this.dsl = dsl;
}
public boolean existsByOgrn(String ogrn) {
return dsl.fetchExists(
dsl.selectFrom(OrganizationAllowed.ORGANIZATION_ALLOWED)
.where(OrganizationAllowed.ORGANIZATION_ALLOWED.OGRN.eq(ogrn))
);
}
}

View file

@ -6,4 +6,6 @@ public class SecurityConstants {
public static final String AUTH_MARKER = "webbpm.ervu-lkrp-ul";
public static final String PRNS_UUID = "prns_uuid_ul";
public static final String STICKY_SESSION = "stickysession";
public static final String UPLOAD_ALLOWED_MARKER = "upload_allowed";
public static final String EMPLOYEE_DOCUMENT_PATH = "/employee/document";
}

View file

@ -62,6 +62,7 @@ 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.service.UploadAccessService;
import ru.cg.webbpm.modules.core.runtime.api.LocalizedException;
import ru.cg.webbpm.modules.core.runtime.api.MessageBundleUtils;
@ -93,6 +94,8 @@ public class EsiaAuthService {
private SecurityHelper securityHelper;
@Autowired
private AuditService auditService;
@Autowired
private UploadAccessService uploadAccessService;
@Value("${ervu.kafka.org.reply.topic}")
private String requestReplyTopic;
@ -191,6 +194,7 @@ public class EsiaAuthService {
String prnOid = null;
Long expiresIn = null;
boolean hasRole = false;
boolean fileUploadAllowed = false;
long timeSignSecret = 0, timeRequestAccessToken = 0, timeVerifySecret = 0;
verifyStateFromCookie(request, state, response);
try {
@ -280,6 +284,7 @@ public class EsiaAuthService {
try {
orgInfo = getOrgInfo(esiaAccessTokenStr);
hasRole = ulDataService.checkRole(esiaAccessTokenStr);
fileUploadAllowed = uploadAccessService.canUploadFiles(orgInfo.getOgrn());
ervuId = getErvuId(prnOid, orgInfo);
if (!hasRole) {
LOGGER.error("The user with id = " + prnOid + " does not have the required role");
@ -298,7 +303,7 @@ public class EsiaAuthService {
auditService.processAuthEvent(request, orgInfo, prnOid, status,
AuditConstants.LOGIN_EVENT_TYPE);
}
createTokenAndAddCookie(response, prnOid, ervuId, hasRole , expiresIn);
createTokenAndAddCookie(response, prnOid, ervuId, hasRole, fileUploadAllowed, expiresIn);
}
}
@ -371,8 +376,9 @@ public class EsiaAuthService {
EsiaAuthInfoStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
EsiaAuthInfoStore.addRefreshToken(prnOid, esiaNewRefreshToken, expiresIn);
OrgInfo orgInfo = getOrgInfo(esiaAccessTokenStr);
boolean fileUploadAllowed = uploadAccessService.canUploadFiles(orgInfo.getOgrn());
String ervuId = getErvuId(prnOid, orgInfo);
createTokenAndAddCookie(response, esiaAccessToken.getSbjId(), ervuId, true, expiresIn);
createTokenAndAddCookie(response, esiaAccessToken.getSbjId(), ervuId, true, fileUploadAllowed, expiresIn);
}
catch (EsiaException | IOException | InterruptedException e) {
throw new EsiaException(e);
@ -548,9 +554,9 @@ public class EsiaAuthService {
}
private void createTokenAndAddCookie(HttpServletResponse response, String userId, String ervuId,
Boolean hasRole, Long expiresIn) {
Token token = jwtTokenService.createAccessToken(userId, expiresIn, ervuId, hasRole);
securityHelper.addAccessCookies(response, token.getValue(), expiresIn.intValue());
Boolean hasRole, Boolean fileUploadAllowed, Long expiresIn) {
Token token = jwtTokenService.createAccessToken(userId, expiresIn, ervuId, hasRole, fileUploadAllowed);
securityHelper.addAccessCookies(response, token.getValue(), expiresIn.intValue(), fileUploadAllowed);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext();

View file

@ -6,6 +6,7 @@ import javax.servlet.http.HttpServletRequest;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
@ -19,10 +20,10 @@ import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;
import static ru.micord.ervu.security.SecurityConstants.EMPLOYEE_DOCUMENT_PATH;
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final JwtTokenService jwtTokenService;
@Autowired
@ -56,6 +57,12 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
}
if (jwtTokenService.isValid(token) && token.getHasRole()) {
String requestPath = request.getRequestURI();
if (EMPLOYEE_DOCUMENT_PATH.equals(requestPath) &&
Boolean.FALSE.equals(token.isFileUploadAllowed())) {
throw new AccessDeniedException("File upload is not allowed for this organization");
}
UsernamePasswordAuthenticationToken pwdToken =
UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null,
Collections.emptyList()

View file

@ -17,6 +17,7 @@ import static org.springframework.web.context.request.RequestAttributes.REFERENC
import static ru.micord.ervu.security.SecurityConstants.AUTH_MARKER;
import static ru.micord.ervu.security.SecurityConstants.AUTH_TOKEN;
import static ru.micord.ervu.security.SecurityConstants.PRNS_UUID;
import static ru.micord.ervu.security.SecurityConstants.UPLOAD_ALLOWED_MARKER;
public final class SecurityHelper {
@Value("${cookie.path:#{null}}")
@ -45,6 +46,14 @@ public final class SecurityHelper {
.httpOnly(false)
.build();
addResponseCookie(response, emptyAuthMarker);
ResponseCookie emptyUploadAllowed = createCookie(UPLOAD_ALLOWED_MARKER, null, "/")
.maxAge(0)
.secure(false)
.httpOnly(false)
.build();
addResponseCookie(response, emptyUploadAllowed);
clearCookie(response, PRNS_UUID, accessCookiePath);
}
@ -52,7 +61,8 @@ public final class SecurityHelper {
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
}
public void addAccessCookies(HttpServletResponse response, String cookieValue, int expiry) {
public void addAccessCookies(HttpServletResponse response, String cookieValue, int expiry,
Boolean fileUploadAllowed) {
ResponseCookie authTokenCookie = createCookie(AUTH_TOKEN, cookieValue, accessCookiePath)
.maxAge(expiry)
.build();
@ -64,6 +74,15 @@ public final class SecurityHelper {
.httpOnly(false)
.build();
addResponseCookie(response, authMarker);
if (fileUploadAllowed) {
ResponseCookie uploadAllowedCookie = createCookie(UPLOAD_ALLOWED_MARKER, "true", "/")
.maxAge(expiry)
.secure(false)
.httpOnly(false)
.build();
addResponseCookie(response, uploadAllowedCookie);
}
}
public ResponseCookie.ResponseCookieBuilder createCookie(String name, String value, String path) {

View file

@ -8,13 +8,16 @@ public class Token {
private final Date expirationDate;
private final String value;
private final Boolean hasRole;
private final Boolean fileUploadAllowed;
public Token(String userAccountId, String issuer, Date expirationDate, String value, Boolean hasRole) {
public Token(String userAccountId, String issuer, Date expirationDate, String value, Boolean hasRole,
Boolean fileUploadAllowed) {
this.userAccountId = userAccountId;
this.issuer = issuer;
this.expirationDate = expirationDate;
this.value = value;
this.hasRole = hasRole;
this.fileUploadAllowed = fileUploadAllowed;
}
public String getUserAccountId() {
@ -40,4 +43,8 @@ public class Token {
public Boolean getHasRole() {
return hasRole;
}
public Boolean isFileUploadAllowed() {
return fileUploadAllowed;
}
}

View file

@ -43,8 +43,7 @@ public class JwtTokenService {
this.signingKey = Keys.hmacShaKeyFor(encodedKey);
}
public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId, Boolean hasRole) {
public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId, Boolean hasRole, Boolean fileUploadAllowed) {
Date expirationDate = new Date(System.currentTimeMillis() + 1000L * expiresIn);
String value = Jwts.builder()
.setSubject(userAccountId + ":" + ervuId)
@ -52,9 +51,12 @@ public class JwtTokenService {
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(expirationDate)
.claim("hasRole", hasRole)
.claim("fileUploadAllowed", fileUploadAllowed)
.signWith(signingKey)
.compact();
return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value, hasRole);
return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value, hasRole,
fileUploadAllowed
);
}
public boolean isValid(Token token) {
@ -77,7 +79,9 @@ public class JwtTokenService {
.parseClaimsJws(token)
.getBody();
return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token, claims.get("hasRole", Boolean.class));
return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token,
claims.get("hasRole", Boolean.class), claims.get("fileUploadAllowed", Boolean.class)
);
}
public String getAccessToken(HttpServletRequest request) {

View file

@ -0,0 +1,32 @@
package ru.micord.ervu.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import ru.micord.ervu.dao.OrganizationAccessDao;
/**
* @author Adel Kalimullin
*/
@Service
public class UploadAccessService {
private final OrganizationAccessDao organizationAccessDao;
private final boolean ogrnCheckEnabled;
public UploadAccessService(
OrganizationAccessDao organizationAccessDao,
@Value("${ervu.file.upload.ogrn.check.enabled:false}") boolean ogrnCheckEnabled
) {
this.organizationAccessDao = organizationAccessDao;
this.ogrnCheckEnabled = ogrnCheckEnabled;
}
public boolean canUploadFiles(String ogrn) {
if (!ogrnCheckEnabled) {
return true;
}
return StringUtils.hasText(ogrn) && organizationAccessDao.existsByOgrn(ogrn);
}
}

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="create-table-organization_allowed" author="adel.ka">
<comment>Create table for organizations allowed to upload files</comment>
<createTable tableName="organization_allowed">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" primaryKeyName="pk_organization_allowed"/>
</column>
<column name="ogrn" type="varchar(15)">
<constraints nullable="false" unique="true" uniqueConstraintName="uni_organization_allowed_ogrn"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>

View file

@ -9,6 +9,6 @@
<include file="2024-09-11--01-create-table-interaction-log.xml" relativeToChangelogFile="true"/>
<include file="2024-09-18--02-add-shedlock-table.xml" relativeToChangelogFile="true"/>
<include file="2025-10-20-create-offset-column.xml" relativeToChangelogFile="true"/>
<include file="2025-10-24-add-org-allowed-table.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View file

@ -135,3 +135,4 @@
- `ERVU_FILE_UPLOAD_MAX_FILE_SIZE` - определяет максимальный размер загружаемого файла в байтах. Указывает предел размера для каждого индивидуального файла, который может быть загружен. Если файл превышает этот размер, загрузка будет прервана, и может быть вызвано исключение.
- `ERVU_FILE_UPLOAD_MAX_REQUEST_SIZE` - устанавливает максимальный общий размер всех файлов в одном многозадачном запросе в байтах. Это ограничение на весь запрос, включающий данные и файлы. Если общий размер запроса превышает этот параметр, загрузка файлов будет остановлена.
- `ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD` - указывает размер (в байтах), при достижении которого файл будет записан во временное хранилище на диск. Это позволяет улучшить производительность, исключая непосредственную запись мелких файлов на диск, если они не превышают указанного порога. Файлы, меньшие этого значения, могут быть сохранены в памяти.
- `ERVU_FILE_UPLOAD_OGRN_CHECK_ENABLED` - флаг активации проверки ОГРН организаций при загрузке файлов

View file

@ -0,0 +1,15 @@
import {Behavior, Visible} from "@webbpm/base-package";
import {CookieService} from "ngx-cookie";
export class FileUploadChecker extends Behavior {
private cookieService: CookieService;
initialize() {
this.cookieService = this.injector.get(CookieService);
}
@Visible()
public fileUploadAllowed(): boolean {
return this.cookieService.get("upload_allowed") != null;
}
}

View file

@ -24,8 +24,12 @@ export class SetFilter implements IFilterComp {
this.selectAll = this.initCheckBox('selectAll', 'Все', index);
this.checkboxes.push(this.selectAll);
params.api.getRenderedNodes()
.map(node => node.data[params.colDef.field])
const allValues: any[] = [];
params.api.forEachNode((node) => {
allValues.push(node.data[params.colDef.field]);
});
allValues
.sort((n1, n2) => n1 > n2 ? 1 : n1 < n2 ? -1 : 0)
.forEach(value => {
if (this.values.includes(value)) {
@ -36,7 +40,8 @@ export class SetFilter implements IFilterComp {
let checkbox = this.initCheckBox(id, value, index);
this.checkboxes.push(checkbox);
this.values.push(value);
});
});
this.initialValues = this.values.slice();
this.filterParams = params;
this.filterActive = false;

View file

@ -172,6 +172,14 @@
<scripts id="fe04d7fb-6c5b-46c4-b723-667732d81f4f"/>
<scripts id="5c566210-2a60-4048-a2d1-84c7dd023248"/>
<scripts id="3171b2e1-b4af-4335-95fa-1b2592604b84"/>
<scripts id="28d0e9ff-7ebc-4297-9bcd-6fdd246e4684">
<classRef type="TS">
<className>FileUploadChecker</className>
<packageName>ervu</packageName>
</classRef>
<enabled>true</enabled>
<expanded>true</expanded>
</scripts>
<children id="829f09dd-e33f-4b1f-90ea-16994e373d7e">
<prototypeId>9d1b5af1-0b8f-4b1b-b9a5-c2e6acf72d91</prototypeId>
<componentRootId>829f09dd-e33f-4b1f-90ea-16994e373d7e</componentRootId>
@ -886,6 +894,7 @@
<componentRootId>1a3543a3-3797-4d65-8319-d88e8ccd34e1</componentRootId>
<name>Диалог - выбор файла и отправка</name>
<container>true</container>
<expanded>false</expanded>
<childrenReordered>false</childrenReordered>
<scripts id="cf4526a1-96ab-4820-8aa9-62fb54c2b64c">
<properties>
@ -912,6 +921,30 @@
<entry>
<key>elseActions</key>
<value>
<item id="dd82f208-b02e-4ced-b85e-a810b5c7a0ec" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"93a1b29d-c3c8-4300-a063-44720562dff3","packageName":"component","className":"Dialog","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"show"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<simple>null</simple>
</value>
</entry>
</complex>
</value>
</item>
<item id="4548b886-ce1c-4241-9377-608c7c225e23" removed="true"/>
</value>
</entry>
@ -942,6 +975,79 @@
<key>ifCondition</key>
<value>
<complex>
<entry>
<key>conditions</key>
<value>
<item id="1b308420-133f-4d33-aedf-31bfafbba42c" removed="false">
<value>
<complex>
<entry>
<key>_isGroupSelected</key>
<value>
<simple>false</simple>
</value>
</entry>
<entry>
<key>one</key>
<value>
<complex>
<entry>
<key>conditionFirstPart</key>
<value>
<complex>
<entry>
<key>objectValue</key>
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"4e49112c-ab94-49d7-b070-a69609516251","packageName":"ervu","className":"FileUploadChecker","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"fileUploadAllowed"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>conditionSecondPart</key>
<value>
<complex>
<entry>
<key>staticValue</key>
<value>
<implRef type="TS">
<className>boolean</className>
<packageName></packageName>
</implRef>
<simple>true</simple>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>operation</key>
<value>
<simple>"EQUALS"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry>
<key>logicalOperation</key>
<value>
@ -4464,6 +4570,35 @@
<childrenReordered>false</childrenReordered>
<scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e">
<properties>
<entry>
<key>elseActions</key>
<value>
<item id="3492ec56-4b3c-4c9a-9fe8-020256e54b92" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"93a1b29d-c3c8-4300-a063-44720562dff3","packageName":"component","className":"Dialog","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"show"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<simple>null</simple>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry>
<key>eventRefs</key>
<value>
@ -4491,6 +4626,79 @@
<key>ifCondition</key>
<value>
<complex>
<entry>
<key>conditions</key>
<value>
<item id="669f848b-1480-48dc-9872-347f9b2efa04" removed="false">
<value>
<complex>
<entry>
<key>_isGroupSelected</key>
<value>
<simple>false</simple>
</value>
</entry>
<entry>
<key>one</key>
<value>
<complex>
<entry>
<key>conditionFirstPart</key>
<value>
<complex>
<entry>
<key>objectValue</key>
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"4e49112c-ab94-49d7-b070-a69609516251","packageName":"ervu","className":"FileUploadChecker","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"fileUploadAllowed"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>conditionSecondPart</key>
<value>
<complex>
<entry>
<key>staticValue</key>
<value>
<implRef type="TS">
<className>boolean</className>
<packageName></packageName>
</implRef>
<simple>true</simple>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>operation</key>
<value>
<simple>"EQUALS"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry>
<key>logicalOperation</key>
<value>
@ -7827,6 +8035,30 @@
<entry>
<key>elseActions</key>
<value>
<item id="1384a2ac-bb7f-479b-8310-fada7e38837b" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"93a1b29d-c3c8-4300-a063-44720562dff3","packageName":"component","className":"Dialog","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"show"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<simple>null</simple>
</value>
</entry>
</complex>
</value>
</item>
<item id="72802f92-6956-437f-b6b6-37d257b3620d" removed="true"/>
</value>
</entry>
@ -7857,6 +8089,79 @@
<key>ifCondition</key>
<value>
<complex>
<entry>
<key>conditions</key>
<value>
<item id="7c9fa148-aeff-42c4-b175-7481aee40fbf" removed="false">
<value>
<complex>
<entry>
<key>_isGroupSelected</key>
<value>
<simple>false</simple>
</value>
</entry>
<entry>
<key>one</key>
<value>
<complex>
<entry>
<key>conditionFirstPart</key>
<value>
<complex>
<entry>
<key>objectValue</key>
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"4e49112c-ab94-49d7-b070-a69609516251","packageName":"ervu","className":"FileUploadChecker","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"fileUploadAllowed"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>conditionSecondPart</key>
<value>
<complex>
<entry>
<key>staticValue</key>
<value>
<implRef type="TS">
<className>boolean</className>
<packageName></packageName>
</implRef>
<simple>true</simple>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>operation</key>
<value>
<simple>"EQUALS"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry>
<key>logicalOperation</key>
<value>
@ -11311,6 +11616,35 @@
<childrenReordered>false</childrenReordered>
<scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e">
<properties>
<entry>
<key>elseActions</key>
<value>
<item id="e9df528c-19d2-428c-b782-c2b0b73cfa16" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"93a1b29d-c3c8-4300-a063-44720562dff3","packageName":"component","className":"Dialog","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"show"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<simple>null</simple>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry>
<key>eventRefs</key>
<value>
@ -11338,6 +11672,79 @@
<key>ifCondition</key>
<value>
<complex>
<entry>
<key>conditions</key>
<value>
<item id="1221dfba-63f0-4f26-9038-f7189e85bbc2" removed="false">
<value>
<complex>
<entry>
<key>_isGroupSelected</key>
<value>
<simple>false</simple>
</value>
</entry>
<entry>
<key>one</key>
<value>
<complex>
<entry>
<key>conditionFirstPart</key>
<value>
<complex>
<entry>
<key>objectValue</key>
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"4e49112c-ab94-49d7-b070-a69609516251","packageName":"ervu","className":"FileUploadChecker","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"fileUploadAllowed"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>conditionSecondPart</key>
<value>
<complex>
<entry>
<key>staticValue</key>
<value>
<implRef type="TS">
<className>boolean</className>
<packageName></packageName>
</implRef>
<simple>true</simple>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>operation</key>
<value>
<simple>"EQUALS"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry>
<key>logicalOperation</key>
<value>
@ -14114,7 +14521,6 @@
<componentRootId>991237d3-8cb9-48af-8501-030a3c8c6cfc</componentRootId>
<name>Группа полей</name>
<container>true</container>
<expanded>false</expanded>
<childrenReordered>false</childrenReordered>
<scripts id="46f20297-81d1-4786-bb17-2a78ca6fda6f">
<properties>
@ -14861,6 +15267,35 @@
<childrenReordered>false</childrenReordered>
<scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e">
<properties>
<entry>
<key>elseActions</key>
<value>
<item id="a388c876-db18-49dc-88b3-c933f7a0d4fa" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"93a1b29d-c3c8-4300-a063-44720562dff3","packageName":"component","className":"Dialog","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"show"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<simple>null</simple>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry>
<key>eventRefs</key>
<value>
@ -14888,6 +15323,79 @@
<key>ifCondition</key>
<value>
<complex>
<entry>
<key>conditions</key>
<value>
<item id="dc796e68-0262-457a-b588-cdeb97c6ce38" removed="false">
<value>
<complex>
<entry>
<key>_isGroupSelected</key>
<value>
<simple>false</simple>
</value>
</entry>
<entry>
<key>one</key>
<value>
<complex>
<entry>
<key>conditionFirstPart</key>
<value>
<complex>
<entry>
<key>objectValue</key>
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"4e49112c-ab94-49d7-b070-a69609516251","packageName":"ervu","className":"FileUploadChecker","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"fileUploadAllowed"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>conditionSecondPart</key>
<value>
<complex>
<entry>
<key>staticValue</key>
<value>
<implRef type="TS">
<className>boolean</className>
<packageName></packageName>
</implRef>
<simple>true</simple>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>operation</key>
<value>
<simple>"EQUALS"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry>
<key>logicalOperation</key>
<value>
@ -18315,4 +18823,207 @@
</children>
</children>
</rootObjects>
<rootObjects id="93a1b29d-c3c8-4300-a063-44720562dff3">
<prototypeId>86f297f1-ab3d-40e0-ac2f-89cc944b7f0a</prototypeId>
<componentRootId>93a1b29d-c3c8-4300-a063-44720562dff3</componentRootId>
<name>Диалог- временное решение для загрузки файла</name>
<container>true</container>
<childrenReordered>false</childrenReordered>
<scripts id="cf4526a1-96ab-4820-8aa9-62fb54c2b64c">
<properties>
<entry>
<key>closable</key>
<value>
<simple>false</simple>
</value>
</entry>
<entry>
<key>cssClasses</key>
<value>
<item id="79b162dd-40b4-4e84-bc19-a64799bb7135" removed="false">
<value>
<simple>"align-center"</simple>
</value>
</item>
<item id="82c81b47-478c-4d32-b810-5a7b887d6905" removed="false">
<value>
<simple>"win-error"</simple>
</value>
</item>
</value>
</entry>
</properties>
</scripts>
<scripts id="48d405ee-5991-4027-bfee-113a895bf8f8"/>
<scripts id="b463917a-16fc-42db-9c92-9c1027e9232e"/>
<scripts id="24a13b10-fec4-420e-aa2e-5af0ec41326a"/>
<scripts id="5f57bb31-85b0-4692-9f1f-d6369dea6e95"/>
<children id="d6e0aa92-78ed-470e-8a98-9cce3f30efe5">
<prototypeId>98594cec-0a9b-4cef-af09-e1b71cb2ad9e</prototypeId>
<componentRootId>d6e0aa92-78ed-470e-8a98-9cce3f30efe5</componentRootId>
<name>Обработка событий-show dialog</name>
<container>false</container>
<removed>true</removed>
</children>
<children id="03e9410a-8d47-4a74-b0c3-cfb8900004f1">
<prototypeId>9d1b5af1-0b8f-4b1b-b9a5-c2e6acf72d91</prototypeId>
<componentRootId>03e9410a-8d47-4a74-b0c3-cfb8900004f1</componentRootId>
<name>Вертикальный контейнер</name>
<container>true</container>
<childrenReordered>false</childrenReordered>
<scripts id="bf098f19-480e-44e4-9084-aa42955c4d0f"/>
<scripts id="72befe90-1915-483f-b88c-d1ec5d4bdc8e"/>
<scripts id="87f3fefa-b77b-4137-aab6-b2bcd83ce380"/>
<scripts id="ef21ca22-3f81-4484-ba6f-58d670c12d4f"/>
<scripts id="277e6fbc-9e2e-4080-bf20-5d8be18e6764"/>
<children id="8e234aed-6ad7-4e00-834f-e043f2d29c55">
<prototypeId>ba24d307-0b91-4299-ba82-9d0b52384ff2</prototypeId>
<componentRootId>8e234aed-6ad7-4e00-834f-e043f2d29c55</componentRootId>
<name>Текст</name>
<container>false</container>
<childrenReordered>false</childrenReordered>
<scripts id="cf4526a1-96ab-4820-8aa9-62fb54c2b64c">
<properties>
<entry>
<key>collectible</key>
<value>
<simple>false</simple>
</value>
</entry>
<entry>
<key>initialValue</key>
<value>
<simple>"Система находится в опытной эксплуатации. Функция загрузки данных от организаций будет доступна позднее."</simple>
</value>
</entry>
</properties>
</scripts>
<scripts id="737b67e2-295f-4356-a1e1-9419344d8c85"/>
<scripts id="a6ccccd9-354c-4725-9d34-c716cf626048"/>
<scripts id="d38c1af5-2bfe-41cd-ab0f-67040f498127"/>
<scripts id="f203f156-be32-4131-9c86-4d6bac6d5d56">
<enabled>false</enabled>
</scripts>
</children>
<children id="b32b3411-2032-4713-8c5c-67cb114ad231">
<prototypeId>fd7e47b9-dce1-4d14-9f3a-580c79f59579</prototypeId>
<componentRootId>b32b3411-2032-4713-8c5c-67cb114ad231</componentRootId>
<name>Кнопка</name>
<container>false</container>
<removed>true</removed>
</children>
</children>
<children id="b28dce91-46cb-46fe-a309-ccc2ace6cb34">
<prototypeId>9d1b5af1-0b8f-4b1b-b9a5-c2e6acf72d91</prototypeId>
<componentRootId>b28dce91-46cb-46fe-a309-ccc2ace6cb34</componentRootId>
<name>Вертикальный контейнер</name>
<container>true</container>
<childrenReordered>false</childrenReordered>
<scripts id="bf098f19-480e-44e4-9084-aa42955c4d0f"/>
<scripts id="72befe90-1915-483f-b88c-d1ec5d4bdc8e"/>
<scripts id="87f3fefa-b77b-4137-aab6-b2bcd83ce380"/>
<scripts id="ef21ca22-3f81-4484-ba6f-58d670c12d4f"/>
<scripts id="277e6fbc-9e2e-4080-bf20-5d8be18e6764"/>
<children id="14c11640-a5b2-4aea-a6a7-4b1ff334de7c">
<prototypeId>98594cec-0a9b-4cef-af09-e1b71cb2ad9e</prototypeId>
<componentRootId>14c11640-a5b2-4aea-a6a7-4b1ff334de7c</componentRootId>
<name>Обработка событий-close dialog</name>
<container>false</container>
<childrenReordered>false</childrenReordered>
<scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e">
<properties>
<entry>
<key>eventRefs</key>
<value>
<item id="f2029195-16c4-4d6f-b9c3-7e0c8d9c3b4c" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"b32b3411-2032-4713-8c5c-67cb114ad231","packageName":"component.button","className":"Button","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>propertyName</key>
<value>
<simple>"successActionEvent"</simple>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry>
<key>ifCondition</key>
<value>
<complex>
<entry>
<key>logicalOperation</key>
<value>
<simple>null</simple>
</value>
</entry>
</complex>
</value>
</entry>
<entry>
<key>thenActions</key>
<value>
<item id="9e014cf6-4245-4475-b0e2-0677ac8f2729" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"93a1b29d-c3c8-4300-a063-44720562dff3","packageName":"component","className":"Dialog","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"hide"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<simple>null</simple>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
</properties>
</scripts>
</children>
<children id="b32b3411-2032-4713-8c5c-67cb114ad231">
<prototypeId>fd7e47b9-dce1-4d14-9f3a-580c79f59579</prototypeId>
<componentRootId>b32b3411-2032-4713-8c5c-67cb114ad231</componentRootId>
<name>Кнопка</name>
<container>false</container>
<childrenReordered>false</childrenReordered>
<scripts id="bf098f19-480e-44e4-9084-aa42955c4d0f">
<properties>
<entry>
<key>caption</key>
<value>
<simple>"Закрыть"</simple>
</value>
</entry>
</properties>
</scripts>
</children>
<children id="f6703dc0-c93c-4d28-adb7-a04faf79a920">
<prototypeId>d7d54cfb-26b5-4dba-b56f-b6247183c24d</prototypeId>
<componentRootId>f6703dc0-c93c-4d28-adb7-a04faf79a920</componentRootId>
<name>Горизонтальный контейнер</name>
<container>true</container>
<removed>true</removed>
</children>
</children>
</rootObjects>
</xmlPage>