Merge remote-tracking branch 'origin/master' into develop

# Conflicts:
#	backend/pom.xml
#	backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java
#	backend/src/main/java/ru/micord/ervu/security/esia/config/EsiaConfig.java
#	backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java
#	backend/src/main/java/ru/micord/ervu/security/esia/token/EsiaTokensStore.java
#	backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java
#	backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java
#	backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/util/SecurityUtil.java
#	config/nginx.conf
#	distribution/pom.xml
#	frontend/pom.xml
#	pom.xml
#	resources/pom.xml
This commit is contained in:
Eduard Tihomirov 2025-01-10 13:03:38 +03:00
commit d362df0e0e
22 changed files with 601 additions and 211 deletions

View file

@ -24,8 +24,8 @@ import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
import ru.micord.ervu.exception.JsonParsingException; import ru.micord.ervu.exception.JsonParsingException;
import ru.micord.ervu.security.esia.token.TokensStore;
import ru.micord.ervu.security.esia.model.EmployeeModel; import ru.micord.ervu.security.esia.model.EmployeeModel;
import ru.micord.ervu.security.esia.model.PersonModel; import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.esia.service.UlDataService; import ru.micord.ervu.security.esia.service.UlDataService;
@ -72,7 +72,7 @@ public class EmployeeInfoFileUploadService {
String[] ids = userAccountId.split(":"); String[] ids = userAccountId.split(":");
String userId = ids[0]; String userId = ids[0];
String ervuId = ids[1]; String ervuId = ids[1];
String accessToken = TokensStore.getAccessToken(userId); String accessToken = EsiaTokensStore.getAccessToken(userId);
EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken);
PersonModel personModel = employeeModel.getPerson(); PersonModel personModel = employeeModel.getPerson();

View file

@ -25,9 +25,7 @@ public class LogoutSuccessHandler
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException { Authentication authentication) throws IOException {
String url = esiaAuthService.logout(request, response); String url = esiaAuthService.logout(request, response);
response.setStatus(HttpServletResponse.SC_OK); response.sendRedirect(url);
response.getWriter().write(url);
response.getWriter().flush();
CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request); CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request);
this.csrfTokenRepository.saveToken(csrfToken, request, response); this.csrfTokenRepository.saveToken(csrfToken, request, response);
} }

View file

@ -105,10 +105,9 @@ public class SecurityConfig {
@Bean @Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper, public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper,
AuthenticationManager manager, AuthenticationManager manager) {
JwtTokenService jwtTokenService) {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter( JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(
new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper, jwtTokenService); new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper);
jwtAuthenticationFilter.setAuthenticationManager(manager); jwtAuthenticationFilter.setAuthenticationManager(manager);
return jwtAuthenticationFilter; return jwtAuthenticationFilter;
} }

View file

@ -29,6 +29,9 @@ public class EsiaConfig {
@Value("${esia.redirect.url}") @Value("${esia.redirect.url}")
private String redirectUrl; private String redirectUrl;
@Value("${esia.logout.redirect.url}")
private String logoutRedirectUrl;
@Value("${sign.url}") @Value("${sign.url}")
private String signUrl; private String signUrl;
@ -107,6 +110,10 @@ public class EsiaConfig {
return esiaTokenUrl; return esiaTokenUrl;
} }
public String getLogoutRedirectUrl() {
return logoutRedirectUrl;
}
public String getSignVerifyUrl() { public String getSignVerifyUrl() {
return signVerifyUrl; return signVerifyUrl;
} }

View file

@ -17,15 +17,14 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import ervu.service.okopf.OkopfService; import ervu.service.okopf.OkopfService;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
import ru.micord.ervu.security.esia.exception.EsiaException; import ru.micord.ervu.security.esia.exception.EsiaException;
import ru.micord.ervu.security.esia.token.TokensStore;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -127,7 +126,7 @@ public class EsiaAuthService {
"obj_type", "B L F A", "obj_type", "B L F A",
"client_certificate_hash", esiaConfig.getClientCertHash()); "client_certificate_hash", esiaConfig.getClientCertHash());
return makeRequest(url, params); return buildUrl(url, params);
} }
catch (Exception e) { catch (Exception e) {
throw new EsiaException(e); throw new EsiaException(e);
@ -154,12 +153,13 @@ public class EsiaAuthService {
} }
} }
private static String makeRequest(URL url, Map<String, String> params) { private static String buildUrl(URL url, Map<String, String> params) {
StringBuilder uriBuilder = new StringBuilder(url.toString()); StringBuilder uriBuilder = new StringBuilder(url.toString());
uriBuilder.append('?'); uriBuilder.append('?');
for (Map.Entry<String, String> node : params.entrySet()) { for (Map.Entry<String, String> node : params.entrySet()) {
uriBuilder.append(node.getKey()).append('=').append(node.getValue()).append("&"); uriBuilder.append(node.getKey()).append('=').append(node.getValue()).append("&");
} }
uriBuilder.deleteCharAt(uriBuilder.length() - 1);
return uriBuilder.toString(); return uriBuilder.toString();
} }
@ -238,8 +238,8 @@ public class EsiaAuthService {
String prnOid = esiaAccessToken.getSbjId(); String prnOid = esiaAccessToken.getSbjId();
String ervuId = getErvuId(esiaAccessTokenStr, prnOid); String ervuId = getErvuId(esiaAccessTokenStr, prnOid);
Long expiresIn = tokenResponse.getExpiresIn(); Long expiresIn = tokenResponse.getExpiresIn();
TokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn); EsiaTokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
TokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn); EsiaTokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn);
createTokenAndAddCookie(response, esiaAccessToken.getSbjId(), ervuId, hasRole, expiresIn); createTokenAndAddCookie(response, esiaAccessToken.getSbjId(), ervuId, hasRole, expiresIn);
if (!hasRole) { if (!hasRole) {
LOGGER.error("The user with id = " + prnOid + " does not have the required role"); LOGGER.error("The user with id = " + prnOid + " does not have the required role");
@ -321,8 +321,8 @@ public class EsiaAuthService {
EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken);
String prnOid = esiaAccessToken.getSbjId(); String prnOid = esiaAccessToken.getSbjId();
Long expiresIn = tokenResponse.getExpiresIn(); Long expiresIn = tokenResponse.getExpiresIn();
TokensStore.addAccessToken(prnOid, accessToken, expiresIn); EsiaTokensStore.addAccessToken(prnOid, accessToken, expiresIn);
TokensStore.addRefreshToken(prnOid, newRefreshToken, expiresIn); EsiaTokensStore.addRefreshToken(prnOid, newRefreshToken, expiresIn);
String ervuId = getErvuId(accessToken, prnOid); String ervuId = getErvuId(accessToken, prnOid);
createTokenAndAddCookie(response, esiaAccessToken.getSbjId(), ervuId, true, expiresIn); createTokenAndAddCookie(response, esiaAccessToken.getSbjId(), ervuId, true, expiresIn);
} }
@ -367,15 +367,15 @@ public class EsiaAuthService {
try { try {
securityHelper.clearAccessCookies(response); securityHelper.clearAccessCookies(response);
String userId = jwtTokenService.getUserAccountId(request); String userId = jwtTokenService.getUserAccountId(request);
TokensStore.removeAccessToken(userId); EsiaTokensStore.removeAccessToken(userId);
TokensStore.removeRefreshToken(userId); EsiaTokensStore.removeRefreshToken(userId);
String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl(); String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl();
String redirectUrl = esiaConfig.getRedirectUrl(); String redirectUrl = esiaConfig.getLogoutRedirectUrl();
URL url = new URL(logoutUrl); URL url = new URL(logoutUrl);
Map<String, String> params = mapOf( Map<String, String> params = mapOf(
"client_id", esiaConfig.getClientId(), "client_id", esiaConfig.getClientId(),
"redirect_url", redirectUrl); "redirect_url", redirectUrl);
return makeRequest(url, params); return buildUrl(url, params);
} }
catch (Exception e) { catch (Exception e) {
throw new EsiaException(e); throw new EsiaException(e);
@ -471,8 +471,7 @@ public class EsiaAuthService {
private void createTokenAndAddCookie(HttpServletResponse response, String userId, String ervuId, private void createTokenAndAddCookie(HttpServletResponse response, String userId, String ervuId,
Boolean hasRole, Long expiresIn) { Boolean hasRole, Long expiresIn) {
Token token = jwtTokenService.createAccessToken(userId, expiresIn, ervuId, hasRole); Token token = jwtTokenService.createAccessToken(userId, expiresIn, ervuId, hasRole);
Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiresIn.intValue()); securityHelper.addAccessCookies(response,token.getValue(), expiresIn.intValue());
response.addCookie(accessCookie);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext(); SecurityContext context = SecurityContextHolder.createEmptyContext();
@ -481,8 +480,6 @@ public class EsiaAuthService {
context.setAuthentication(authentication); context.setAuthentication(authentication);
authenticationManager.authenticate(authentication); authenticationManager.authenticate(authentication);
SecurityContextHolder.setContext(context); SecurityContextHolder.setContext(context);
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiresIn.intValue());
response.addCookie(authMarkerCookie);
} }
private String verifyToken(String accessToken) { private String verifyToken(String accessToken) {

View file

@ -1,12 +1,18 @@
package ru.micord.ervu.security.esia.token; package ru.micord.ervu.security.esia.token;
import java.lang.invoke.MethodHandles;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.CredentialsExpiredException;
/** /**
* @author Eduard Tihomirov * @author Eduard Tihomirov
*/ */
public class TokensStore { public class EsiaTokensStore {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Map<String, ExpiringToken> ACCESS_TOKENS_MAP = new ConcurrentHashMap<>(); private static final Map<String, ExpiringToken> ACCESS_TOKENS_MAP = new ConcurrentHashMap<>();
private static final Map<String, ExpiringToken> REFRESH_TOKENS_MAP = new ConcurrentHashMap<>(); private static final Map<String, ExpiringToken> REFRESH_TOKENS_MAP = new ConcurrentHashMap<>();
@ -21,6 +27,19 @@ public class TokensStore {
return ACCESS_TOKENS_MAP.get(prnOid).getAccessToken(); return ACCESS_TOKENS_MAP.get(prnOid).getAccessToken();
} }
public static boolean validateAccessToken(String prnOid) {
ExpiringToken token = ACCESS_TOKENS_MAP.get(prnOid);
if (token == null || token.getAccessToken() == null) {
LOGGER.error("No ESIA access token for prnOid: " + prnOid);
return false;
}
else if (token.isExpired()) {
LOGGER.error("ESIA access token expired for prnOid: " + prnOid);
return false;
}
return true;
}
public static void removeExpiredAccessToken() { public static void removeExpiredAccessToken() {
for (String key : ACCESS_TOKENS_MAP.keySet()) { for (String key : ACCESS_TOKENS_MAP.keySet()) {
ExpiringToken token = ACCESS_TOKENS_MAP.get(key); ExpiringToken token = ACCESS_TOKENS_MAP.get(key);

View file

@ -14,7 +14,7 @@ public class TokensClearShedulerService {
@SchedulerLock(name = "clearToken") @SchedulerLock(name = "clearToken")
@Transactional @Transactional
public void load() { public void load() {
TokensStore.removeExpiredRefreshToken(); EsiaTokensStore.removeExpiredRefreshToken();
TokensStore.removeExpiredAccessToken(); EsiaTokensStore.removeExpiredAccessToken();
} }
} }

View file

@ -2,6 +2,8 @@ package ru.micord.ervu.security.webbpm.jwt;
import java.util.Collections; import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
@ -11,9 +13,13 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;
@Component @Component
public class JwtAuthenticationProvider implements AuthenticationProvider { public class JwtAuthenticationProvider implements AuthenticationProvider {
@ -42,16 +48,26 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
throw new BadCredentialsException("Authentication Failed.", e); throw new BadCredentialsException("Authentication Failed.", e);
} }
if (!jwtTokenService.isValid(token)) { if (jwtTokenService.isValid(token)) {
throw new BadCredentialsException("Auth token is not valid for user " + token.getUserAccountId()); RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
REFERENCE_REQUEST);
if (request == null) {
throw new IllegalStateException("No request found in request attributes");
}
if (request.getRequestURI().endsWith("esia/logout") || token.getHasRole()) {
UsernamePasswordAuthenticationToken pwdToken =
UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null,
Collections.emptyList()
);
return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
}
} }
UsernamePasswordAuthenticationToken pwdToken = throw new BadCredentialsException(
UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null, "Auth token is not valid for user " + token.getUserAccountId());
Collections.emptyList()
);
return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue());
} }
@Override @Override

View file

@ -7,10 +7,8 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import io.jsonwebtoken.ExpiredJwtException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -19,8 +17,6 @@ import org.springframework.security.web.authentication.AbstractAuthenticationPro
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper; 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; import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken;
@ -36,22 +32,24 @@ public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFil
private final SecurityHelper securityHelper; private final SecurityHelper securityHelper;
private final JwtTokenService jwtTokenService;
public JwtAuthenticationFilter(RequestMatcher requestMatcher, public JwtAuthenticationFilter(RequestMatcher requestMatcher,
AuthenticationEntryPoint entryPoint, AuthenticationEntryPoint entryPoint,
SecurityHelper securityHelper, SecurityHelper securityHelper) {
JwtTokenService jwtTokenService) {
super(requestMatcher); super(requestMatcher);
this.entryPoint = entryPoint; this.entryPoint = entryPoint;
this.securityHelper = securityHelper; this.securityHelper = securityHelper;
this.jwtTokenService = jwtTokenService;
} }
@Override @Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, public Authentication attemptAuthentication(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) HttpServletResponse httpServletResponse)
throws AuthenticationException { throws AuthenticationException {
String browser = httpServletRequest.getHeader("User-Agent");
if (browser == null || (!browser.contains("YaBrowser") && !browser.contains("Chromium GOST"))) {
LOGGER.error("Invalid browser. Use YaBrowser or Chromium GOST");
return clearCookieAndSetStatus(httpServletResponse);
}
String tokenStr = extractAuthToken(httpServletRequest); String tokenStr = extractAuthToken(httpServletRequest);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) { if (authentication == null) {
@ -59,22 +57,10 @@ public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
try { try {
authentication = getAuthenticationManager().authenticate(authentication); authentication = getAuthenticationManager().authenticate(authentication);
String browser = httpServletRequest.getHeader("User-Agent");
if (browser == null || (!browser.contains("YaBrowser") && !browser.contains("Chromium GOST"))) {
throw new CredentialsExpiredException("Invalid browser. Use YaBrowser or Chromium GOST");
}
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) { catch (AuthenticationException e) {
securityHelper.clearAccessCookies(httpServletResponse);
httpServletResponse.setStatus(401);
LOGGER.warn(e.getMessage()); LOGGER.warn(e.getMessage());
return null; return clearCookieAndSetStatus(httpServletResponse);
} }
return authentication; return authentication;
} }
@ -96,4 +82,10 @@ public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFil
SecurityContextHolder.clearContext(); SecurityContextHolder.clearContext();
entryPoint.commence(request, response, exception); entryPoint.commence(request, response, exception);
} }
private Authentication clearCookieAndSetStatus(HttpServletResponse httpServletResponse) {
securityHelper.clearAccessCookies(httpServletResponse);
httpServletResponse.setStatus(401);
return null;
}
} }

View file

@ -1,43 +1,89 @@
package ru.micord.ervu.security.webbpm.jwt.helper; package ru.micord.ervu.security.webbpm.jwt.helper;
import javax.servlet.http.Cookie; import java.net.IDN;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil; import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.AUTH_MARKER; 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.AUTH_TOKEN;
import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.createCookie;
public final class SecurityHelper { public final class SecurityHelper {
@Value("${cookie.path:#{null}}") @Value("${cookie.path:#{null}}")
private String accessCookiePath; private String accessCookiePath;
@Value("${cookie.domain:#{null}}")
private String accessCookieDomain;
@Value("${cookie.secure:false}")
private boolean accessCookieSecure;
@Value("${cookie.same.site:Lax}")
private String accessCookieSameSite;
@PostConstruct
private void init() {
if (accessCookieDomain != null) {
accessCookieDomain = IDN.toASCII(accessCookieDomain);
}
}
public void clearAccessCookies(HttpServletResponse response) { public void clearAccessCookies(HttpServletResponse response) {
Cookie tokenCookie = createCookie(AUTH_TOKEN, null, null); ResponseCookie emptyAuthToken = createCookie(AUTH_TOKEN, null, accessCookiePath)
tokenCookie.setMaxAge(0); .maxAge(0).build();
tokenCookie.setPath(accessCookiePath); addResponseCookie(response, emptyAuthToken);
tokenCookie.setHttpOnly(true);
response.addCookie(tokenCookie);
Cookie markerCookie = createCookie(AUTH_MARKER, null, null); ResponseCookie emptyAuthMarker = createCookie(AUTH_MARKER, null, "/")
markerCookie.setMaxAge(0); .maxAge(0)
markerCookie.setPath("/"); .secure(false)
response.addCookie(markerCookie); .httpOnly(false)
.build();
addResponseCookie(response, emptyAuthMarker);
} }
public Cookie createAccessCookie(String cookieValue, int expiry) { private void addResponseCookie(HttpServletResponse response, ResponseCookie cookie) {
Cookie authToken = createCookie(SecurityUtil.AUTH_TOKEN, cookieValue, accessCookiePath); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
authToken.setPath(accessCookiePath);
authToken.setMaxAge(expiry);
return authToken;
} }
public Cookie createAuthMarkerCookie(String cookieValue, int expiry) { public void addAccessCookies(HttpServletResponse response, String cookieValue, int expiry) {
Cookie marker = createCookie(AUTH_MARKER, cookieValue, "/"); ResponseCookie authTokenCookie = createCookie(AUTH_TOKEN, cookieValue, accessCookiePath)
marker.setMaxAge(expiry); .maxAge(expiry)
marker.setHttpOnly(false); .build();
return marker; addResponseCookie(response, authTokenCookie);
ResponseCookie authMarker = createCookie(AUTH_MARKER, "true", "/")
.maxAge(expiry)
.secure(false)
.httpOnly(false)
.build();
addResponseCookie(response, authMarker);
}
public ResponseCookie.ResponseCookieBuilder createCookie(String name, String value, String path) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
throw new IllegalStateException("Must be called only in request context");
}
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(
REFERENCE_REQUEST);
if (request == null) {
throw new IllegalStateException("Must be called only in request context");
}
String cookieValue = value != null ? URLEncoder.encode(value, StandardCharsets.UTF_8) : "";
return ResponseCookie.from(name, cookieValue)
.path(path != null ? path : request.getContextPath())
.httpOnly(true)
.domain(accessCookieDomain)
.secure(accessCookieSecure)
.sameSite(accessCookieSameSite);
} }
} }

View file

@ -14,8 +14,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ru.micord.ervu.security.esia.token.EsiaTokensStore;
import ru.micord.ervu.security.exception.UnauthorizedException; import ru.micord.ervu.security.exception.UnauthorizedException;
import ru.micord.ervu.security.esia.token.TokensStore;
import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils; import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils;
@ -67,7 +67,8 @@ public class JwtTokenService {
LOGGER.info("Token {} is expired ", token.getValue()); LOGGER.info("Token {} is expired ", token.getValue());
return false; return false;
} }
return true; String[] ids = token.getUserAccountId().split(":");
return EsiaTokensStore.validateAccessToken(ids[0]);
} }
public Token getToken(String token) { public Token getToken(String token) {
@ -80,11 +81,11 @@ public class JwtTokenService {
} }
public String getAccessToken(HttpServletRequest request) { public String getAccessToken(HttpServletRequest request) {
return TokensStore.getAccessToken(getUserAccountId(request)); return EsiaTokensStore.getAccessToken(getUserAccountId(request));
} }
public String getRefreshToken(HttpServletRequest request) { public String getRefreshToken(HttpServletRequest request) {
return TokensStore.getRefreshToken(getUserAccountId(request)); return EsiaTokensStore.getRefreshToken(getUserAccountId(request));
} }
public String getUserAccountId(HttpServletRequest request) { public String getUserAccountId(HttpServletRequest request) {

View file

@ -1,20 +1,14 @@
package ru.micord.ervu.security.webbpm.jwt.util; package ru.micord.ervu.security.webbpm.jwt.util;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Optional; import java.util.Optional;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;
public final class SecurityUtil { public final class SecurityUtil {
public static final String AUTH_TOKEN = "auth_token"; public static final String AUTH_TOKEN = "auth_token";
@ -24,24 +18,6 @@ public final class SecurityUtil {
//empty //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) { public static String extractAuthToken(HttpServletRequest httpRequest) {
Cookie cookie = WebUtils.getCookie(httpRequest, AUTH_TOKEN); Cookie cookie = WebUtils.getCookie(httpRequest, AUTH_TOKEN);
return cookie != null ? cookie.getValue() : null; return cookie != null ? cookie.getValue() : null;

View file

@ -768,9 +768,13 @@ JBPM использует 3 корневых категории логирова
- `ESIA_CLIENT_ID` - идентификатор системы-клиента (мнемоника системы в ЕСИА указанная прописными буквами) - `ESIA_CLIENT_ID` - идентификатор системы-клиента (мнемоника системы в ЕСИА указанная прописными буквами)
- `ESIA_REDIRECT_URL` - ссылка, по которой должен быть направлен пользователь - `ESIA_REDIRECT_URL` - ссылка, по которой должен быть направлен пользователь
после того, как ЕСИА даст разрешение на доступ к ресурсу. после того, как ЕСИА даст разрешение на доступ к ресурсу.
Важно: ESIA_REDIRECT_URL должна содержать полный адрес вплоть до последнего слэша Важно: `ESIA_REDIRECT_URL` должна содержать полный адрес вплоть до последнего слэша
- https://lkul.ervu.loc/ - правильное значение параметра - https://lkul.ervu.loc/ - правильное значение параметра
- https://lkul.ervu.loc - неправильное значение параметра - https://lkul.ervu.loc - неправильное значение параметра
- `ESIA_LOGOUT_REDIRECT_URL` - ссылка, по которой должен быть направлен пользователь после logout-a
Важно: `ESIA_LOGOUT_REDIRECT_URL` должна содержать полный адрес вплоть до последнего слэша:
> - https://lkul.ervu.loc/home.html - правильное значение параметра
> - https://lkul.ervu.loc - неправильное значение параметра
- `ESIA_UPLOAD_DATA_ROLE` - мнемоника группы, для роли "Сотрудник, ответственный за военно-учетную работу". - `ESIA_UPLOAD_DATA_ROLE` - мнемоника группы, для роли "Сотрудник, ответственный за военно-учетную работу".
- `SIGN_URL` - url для подписания с помощью КриптоПро секрета клиента, необходимого для аутентификации через ЕСИА. - `SIGN_URL` - url для подписания с помощью КриптоПро секрета клиента, необходимого для аутентификации через ЕСИА.

View file

@ -22,6 +22,7 @@ ESIA_BASE_URI=https://esia-portal1.test.gosuslugi.ru/
ESIA_ISSUER_URL=http://esia-portal1.test.gosuslugi.ru/ ESIA_ISSUER_URL=http://esia-portal1.test.gosuslugi.ru/
ESIA_CLIENT_ID=MNSV89 ESIA_CLIENT_ID=MNSV89
ESIA_REDIRECT_URL=https://lkrp-dev.micord.ru/ul/ ESIA_REDIRECT_URL=https://lkrp-dev.micord.ru/ul/
ESIA_LOGOUT_REDIRECT_URL=https://lkrp-dev.micord.ru/ul/home.html
ESIA_UPLOAD_DATA_ROLE=MNSV89_UPLOAD_DATA ESIA_UPLOAD_DATA_ROLE=MNSV89_UPLOAD_DATA
SIGN_URL=https://ervu-sign-dev.k8s.micord.ru/sign SIGN_URL=https://ervu-sign-dev.k8s.micord.ru/sign
SIGN_VERIFY_URL=https://ervu-sign-dev.k8s.micord.ru/verify SIGN_VERIFY_URL=https://ervu-sign-dev.k8s.micord.ru/verify

View file

@ -78,12 +78,7 @@ http {
root /frontend; root /frontend;
index index.html; index index.html;
try_files $uri @index; try_files $uri @index;
#Application config
location = /src/resources/app-config.json {
add_header Cache-Control "no-cache";
expires 0;
}
# Media: images, icons, video, audio, HTC # Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|mp3|ogg|ogv|webm|htc|woff2|woff|ttf)$ { location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|mp3|ogg|ogv|webm|htc|woff2|woff|ttf)$ {
expires 1M; expires 1M;
@ -101,7 +96,7 @@ http {
location @index { location @index {
root /frontend; root /frontend;
add_header Cache-Control "no-cache"; add_header Cache-Control no-cache;
expires 0; expires 0;
try_files /index.html =404; try_files /index.html =404;
} }

View file

@ -31,7 +31,7 @@
<div class="container-inside"> <div class="container-inside">
<div class="list-group lk-what"> <div class="list-group lk-what">
<div> <div>
<div class="title short-text"><a href="/">Личный кабинет</a> для ведения воинского учета в организациях</div> <div class="title short-text"><a href=".">Личный кабинет</a> для ведения воинского учета в организациях</div>
<div class="paragraph"> <div class="paragraph">
<div class="paragraph-left"> <div class="paragraph-left">
<div class="text icon-checklist">Кому доступен личный кабинет?</div> <div class="text icon-checklist">Кому доступен личный кабинет?</div>

View file

@ -4,6 +4,9 @@
<title>Личный кабинет юр.лица</title> <title>Личный кабинет юр.лица</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'unsafe-inline' 'self' data:; font-src 'self' data:; img-src 'self' data:"/>
<meta name="referrer" content="strict-origin-when-cross-origin"/>
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/> <link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
</head> </head>
<body webbpm class="webbpm ervu_lkrp_ul"> <body webbpm class="webbpm ervu_lkrp_ul">

View file

@ -1,7 +1,10 @@
<form #logoutForm method="post" [action]="formAction" hidden style="display: none">
<input type="hidden" name="_csrf" [value]="csrfValue"/>
</form>
<button class="user-info" ngbDropdownToggle *ngIf="isAuthenticated()">{{getUserFullname()}}</button> <button class="user-info" ngbDropdownToggle *ngIf="isAuthenticated()">{{getUserFullname()}}</button>
<div ngbDropdownMenu *ngIf="isAuthenticated()"> <div ngbDropdownMenu *ngIf="isAuthenticated()">
<div class="user-department">{{getOrgUnitName()}}</div> <div class="user-department">{{getOrgUnitName()}}</div>
<a routerLink="/mydata" class="data">Данные организации</a> <a routerLink="/mydata" class="data">Данные организации</a>
<button ngbDropdownItem class="exit" (click)="logout()">Выйти</button> <button ngbDropdownItem class="exit" (click)="logoutForm.submit()">Выйти</button>
</div> </div>
<button class="exit" *ngIf="!isAuthenticated()" (click)="logout()">Выйти</button> <button class="exit" *ngIf="!isAuthenticated()" (click)="logoutForm.submit()">Выйти</button>

View file

@ -1,4 +1,5 @@
import { import {
Event,
GridColumnIdUtils, GridColumnIdUtils,
GridRow, GridRow,
GridRowModelType, GridRowModelType,
@ -7,7 +8,7 @@ import {
} from "@webbpm/base-package"; } from "@webbpm/base-package";
import {ChangeDetectionStrategy, Component} from "@angular/core"; import {ChangeDetectionStrategy, Component} from "@angular/core";
import { import {
ColDef, ColDef, FilterChangedEvent,
ICellRendererParams, ICellRendererParams,
ITooltipParams, ITooltipParams,
ValueFormatterParams, ValueFormatterParams,
@ -24,6 +25,9 @@ import {StaticGridColumn} from "../../../generated/ru/micord/ervu/property/grid/
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class InMemoryStaticGrid extends GridV2 { export class InMemoryStaticGrid extends GridV2 {
// todo: remove on updating platform version up to 3.188
@Visible("false")
public columnFiltersChanged: Event<any> = new Event<any>();
private rpcService: InMemoryStaticGridRpcService; private rpcService: InMemoryStaticGridRpcService;
@ -109,4 +113,17 @@ export class InMemoryStaticGrid extends GridV2 {
public getRowDataSize(): number { public getRowDataSize(): number {
return this.rowData ? this.rowData.length : 0; return this.rowData ? this.rowData.length : 0;
} }
// todo: remove on updating platform version up to 3.188
@Visible()
public hasColumnFilters(): boolean {
const filterModel: { [key: string]: any; } = this.gridApi.getFilterModel();
return !!filterModel && Object.keys(filterModel).length > 0;
}
// todo: remove on updating platform version up to 3.188
public columnFilterChanged(event: FilterChangedEvent) {
this.columnFiltersChanged.trigger(event);
super.columnFilterChanged(event);
}
} }

View file

@ -1,23 +1,46 @@
import {ChangeDetectorRef, Component, OnInit} from "@angular/core"; import {ChangeDetectorRef, Component, DoCheck, OnInit} from "@angular/core";
import {Router} from "@angular/router"; import {HttpClient, HttpXsrfTokenExtractor} from "@angular/common/http";
import {HttpClient} from "@angular/common/http";
import {AuthenticationService} from "../../security/authentication.service"; import {AuthenticationService} from "../../security/authentication.service";
import {AppConfigService} from "@webbpm/base-package";
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
selector: "[log-out]", selector: "[log-out]",
templateUrl: "../../../../../src/resources/template/app/component/log_out.html" templateUrl: "../../../../../src/resources/template/app/component/log_out.html"
}) })
export class LogOutComponent implements OnInit{ export class LogOutComponent implements OnInit, DoCheck{
private static readonly BACKEND_URL: string = "backend.url";
private static readonly BACKEND_CONTEXT: string = "backend.context";
private static readonly LOGOUT_URL_POSTFIX: string = "/esia/logout";
private userFullname: string; private userFullname: string;
private orgUnitName: string; private orgUnitName: string;
csrfValue: any;
formAction: any;
constructor(private router: Router, private httpClient: HttpClient, constructor(private httpClient: HttpClient,
private authenticationService: AuthenticationService, private cd: ChangeDetectorRef) { private authenticationService: AuthenticationService,
private appConfigService: AppConfigService,
private tokenExtractor: HttpXsrfTokenExtractor,
private cd: ChangeDetectorRef) {
let backendUrl = this.appConfigService.getParamValue(LogOutComponent.BACKEND_URL);
let backendContext = this.appConfigService.getParamValue(
LogOutComponent.BACKEND_CONTEXT);
if (backendUrl) {
this.formAction = `${backendUrl}${LogOutComponent.LOGOUT_URL_POSTFIX}`;
}
else if (backendContext) {
this.formAction = `/${backendContext}${LogOutComponent.LOGOUT_URL_POSTFIX}`;
}
}
ngDoCheck(): void {
this.csrfValue = this.tokenExtractor.getToken();
} }
ngOnInit(): void { ngOnInit(): void {
this.csrfValue = this.tokenExtractor.getToken();
let isAuth = this.authenticationService.isAuthenticated(); let isAuth = this.authenticationService.isAuthenticated();
if (isAuth) { if (isAuth) {
Promise.all([ Promise.all([
@ -31,10 +54,6 @@ export class LogOutComponent implements OnInit{
} }
} }
public logout(): void {
this.authenticationService.logout();
}
public getUserFullname(): string { public getUserFullname(): string {
return this.userFullname; return this.userFullname;
} }

View file

@ -307,13 +307,224 @@
</item> </item>
</value> </value>
</entry> </entry>
<entry>
<key>visible</key>
<value>
<simple>false</simple>
</value>
</entry>
</properties>
</scripts>
</children>
<children id="2332e342-cb03-48cf-9745-e1bfbad545d2">
<prototypeId>98594cec-0a9b-4cef-af09-e1b71cb2ad9e</prototypeId>
<componentRootId>2332e342-cb03-48cf-9745-e1bfbad545d2</componentRootId>
<name>AC обновление текста</name>
<container>false</container>
<childrenReordered>false</childrenReordered>
<scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e">
<properties>
<entry>
<key>eventRefs</key>
<value>
<item id="d58cd6c5-54b7-400b-8ecf-91bac319c026" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"9b7c3369-e1fe-44f6-88f9-f8d9c83b30dc","packageName":"component.field","className":"NumberField","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>propertyName</key>
<value>
<simple>"valueChangeEvent"</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="faffc82c-f944-4755-af42-c1b192ad27f0" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"7f891535-8dde-4e00-8064-584cad7ffcfd","packageName":"component","className":"Text","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"setValue"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<complex>
<entry>
<key>objectValue</key>
<value>
<complex>
<entry>
<key>argument</key>
<value>
<simple>null</simple>
</value>
</entry>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"9b7c3369-e1fe-44f6-88f9-f8d9c83b30dc","packageName":"component.field","className":"NumberField","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"getTextValue"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
</properties>
</scripts>
</children>
<children id="7f600c0d-ad59-46ad-9aa8-037fdfaaac14">
<prototypeId>98594cec-0a9b-4cef-af09-e1b71cb2ad9e</prototypeId>
<componentRootId>7f600c0d-ad59-46ad-9aa8-037fdfaaac14</componentRootId>
<name>AC установка количества</name>
<container>false</container>
<childrenReordered>false</childrenReordered>
<scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e">
<properties>
<entry>
<key>eventRefs</key>
<value>
<item id="5c42b561-31e1-4b00-a4fb-66bb8005f574" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"bbaf33d7-0679-440b-a394-cb805ce80300","packageName":"ervu.component.grid","className":"InMemoryStaticGrid","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>propertyName</key>
<value>
<simple>"gridLoaded"</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="2a4b91ec-24a4-4cdd-93f2-e5f0052754f0" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"9b7c3369-e1fe-44f6-88f9-f8d9c83b30dc","packageName":"component.field","className":"NumberField","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"setValue"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<complex>
<entry>
<key>objectValue</key>
<value>
<complex>
<entry>
<key>argument</key>
<value>
<simple>null</simple>
</value>
</entry>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"bbaf33d7-0679-440b-a394-cb805ce80300","packageName":"ervu.component.grid","className":"InMemoryStaticGrid","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"getRowDataSize"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
<item id="5ce9b480-f5b5-4510-8b40-f97ab3923cc1" removed="true"/>
</value>
</entry>
</properties> </properties>
</scripts> </scripts>
</children> </children>
<children id="6022b88c-7c96-4961-9a03-042d71e4ac16"> <children id="6022b88c-7c96-4961-9a03-042d71e4ac16">
<prototypeId>98594cec-0a9b-4cef-af09-e1b71cb2ad9e</prototypeId> <prototypeId>98594cec-0a9b-4cef-af09-e1b71cb2ad9e</prototypeId>
<componentRootId>6022b88c-7c96-4961-9a03-042d71e4ac16</componentRootId> <componentRootId>6022b88c-7c96-4961-9a03-042d71e4ac16</componentRootId>
<name>Обработка событий</name> <name>AC нажатие кнопки</name>
<container>false</container> <container>false</container>
<childrenReordered>false</childrenReordered> <childrenReordered>false</childrenReordered>
<scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e"> <scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e">
@ -431,6 +642,42 @@
</complex> </complex>
</value> </value>
</item> </item>
<item id="e748c5a2-b2b2-4b90-9c63-27a4ffb8ab89" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"26242dda-11df-42f0-be93-9fe67ccc016e","packageName":"component.button","className":"Button","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"setVisible"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<complex>
<entry>
<key>staticValue</key>
<value>
<implRef type="TS">
<className>boolean</className>
<packageName></packageName>
</implRef>
<simple>false</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
<item id="482bf3b8-6b8d-4ea3-9bb7-4379b874d29e" removed="true"/>
<item id="206ab213-5d49-43d5-a825-d6534d934cd7" removed="true"/> <item id="206ab213-5d49-43d5-a825-d6534d934cd7" removed="true"/>
<item id="3cb96386-a113-4cab-8046-74fb91f49b89" removed="true"/> <item id="3cb96386-a113-4cab-8046-74fb91f49b89" removed="true"/>
</value> </value>
@ -438,18 +685,59 @@
</properties> </properties>
</scripts> </scripts>
</children> </children>
<children id="7f600c0d-ad59-46ad-9aa8-037fdfaaac14"> <children id="dec5e904-4f52-444e-8478-d873461616d6">
<prototypeId>98594cec-0a9b-4cef-af09-e1b71cb2ad9e</prototypeId> <prototypeId>98594cec-0a9b-4cef-af09-e1b71cb2ad9e</prototypeId>
<componentRootId>7f600c0d-ad59-46ad-9aa8-037fdfaaac14</componentRootId> <componentRootId>dec5e904-4f52-444e-8478-d873461616d6</componentRootId>
<name>Action Controller</name> <name>AC доступность кнопки</name>
<container>false</container> <container>false</container>
<childrenReordered>false</childrenReordered> <childrenReordered>false</childrenReordered>
<scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e"> <scripts id="37dff5c8-1599-4984-b107-c44a87b6da2e">
<properties> <properties>
<entry>
<key>elseActions</key>
<value>
<item id="4dea5181-d928-4e7e-944c-6ef6e3bffb9a" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"26242dda-11df-42f0-be93-9fe67ccc016e","packageName":"component.button","className":"Button","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"setVisible"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<complex>
<entry>
<key>staticValue</key>
<value>
<implRef type="TS">
<className>boolean</className>
<packageName></packageName>
</implRef>
<simple>false</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
</value>
</entry>
<entry> <entry>
<key>eventRefs</key> <key>eventRefs</key>
<value> <value>
<item id="5c42b561-31e1-4b00-a4fb-66bb8005f574" removed="false"> <item id="a7b50f11-35a6-41c2-b173-b620f9ee2bc1" removed="true"/>
<item id="b80eaeda-73fb-4357-8c8e-9b025e7f2aac" removed="false">
<value> <value>
<complex> <complex>
<entry> <entry>
@ -461,18 +749,92 @@
<entry> <entry>
<key>propertyName</key> <key>propertyName</key>
<value> <value>
<simple>"gridLoaded"</simple> <simple>"columnFiltersChanged"</simple>
</value> </value>
</entry> </entry>
</complex> </complex>
</value> </value>
</item> </item>
<item id="742e3283-f002-444d-9081-730ee368fd19" removed="true"/>
</value> </value>
</entry> </entry>
<entry> <entry>
<key>ifCondition</key> <key>ifCondition</key>
<value> <value>
<complex> <complex>
<entry>
<key>conditions</key>
<value>
<item id="e0fea419-f8f0-4367-b983-525782b27cd3" 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":"bbaf33d7-0679-440b-a394-cb805ce80300","packageName":"ervu.component.grid","className":"InMemoryStaticGrid","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"hasColumnFilters"</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> <entry>
<key>logicalOperation</key> <key>logicalOperation</key>
<value> <value>
@ -485,19 +847,19 @@
<entry> <entry>
<key>thenActions</key> <key>thenActions</key>
<value> <value>
<item id="2a4b91ec-24a4-4cdd-93f2-e5f0052754f0" removed="false"> <item id="96cc9b36-a8a2-48ab-b225-bbfe30b8124c" removed="false">
<value> <value>
<complex> <complex>
<entry> <entry>
<key>behavior</key> <key>behavior</key>
<value> <value>
<simple>{"objectId":"9b7c3369-e1fe-44f6-88f9-f8d9c83b30dc","packageName":"component.field","className":"NumberField","type":"TS"}</simple> <simple>{"objectId":"26242dda-11df-42f0-be93-9fe67ccc016e","packageName":"component.button","className":"Button","type":"TS"}</simple>
</value> </value>
</entry> </entry>
<entry> <entry>
<key>method</key> <key>method</key>
<value> <value>
<simple>"setValue"</simple> <simple>"setVisible"</simple>
</value> </value>
</entry> </entry>
<entry> <entry>
@ -505,78 +867,13 @@
<value> <value>
<complex> <complex>
<entry> <entry>
<key>objectValue</key> <key>staticValue</key>
<value> <value>
<complex> <implRef type="TS">
<entry> <className>boolean</className>
<key>argument</key> <packageName></packageName>
<value> </implRef>
<simple>null</simple> <simple>true</simple>
</value>
</entry>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"bbaf33d7-0679-440b-a394-cb805ce80300","packageName":"ervu.component.grid","className":"InMemoryStaticGrid","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"getRowDataSize"</simple>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</entry>
</complex>
</value>
</item>
<item id="5ce9b480-f5b5-4510-8b40-f97ab3923cc1" removed="false">
<value>
<complex>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"7f891535-8dde-4e00-8064-584cad7ffcfd","packageName":"component","className":"Text","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"setValue"</simple>
</value>
</entry>
<entry>
<key>value</key>
<value>
<complex>
<entry>
<key>objectValue</key>
<value>
<complex>
<entry>
<key>argument</key>
<value>
<simple>null</simple>
</value>
</entry>
<entry>
<key>behavior</key>
<value>
<simple>{"objectId":"9b7c3369-e1fe-44f6-88f9-f8d9c83b30dc","packageName":"component.field","className":"NumberField","type":"TS"}</simple>
</value>
</entry>
<entry>
<key>method</key>
<value>
<simple>"getTextValue"</simple>
</value>
</entry>
</complex>
</value> </value>
</entry> </entry>
</complex> </complex>

View file

@ -10618,7 +10618,7 @@
<entry> <entry>
<key>initialValue</key> <key>initialValue</key>
<value> <value>
<simple>"Информацию о статусе приема сведений можно отслеживать в \u003ca href\u003d\"/#/filesentlog\"\u003eЖурнале взаимодействий с Реестром ВУ\u003c/a\u003e"</simple> <simple>"Информацию о статусе приема сведений можно отслеживать в \u003ca href\u003d\"./#/filesentlog\"\u003eЖурнале взаимодействий с Реестром ВУ\u003c/a\u003e"</simple>
</value> </value>
</entry> </entry>
</properties> </properties>