Merge branch 'master' into develop

# Conflicts:
#	backend/pom.xml
#	distribution/pom.xml
#	frontend/pom.xml
#	pom.xml
#	resources/pom.xml
This commit is contained in:
Zaripov Emil 2024-11-20 11:16:55 +03:00
commit 67214fccdc
11 changed files with 194 additions and 57 deletions

View file

@ -1,10 +1,13 @@
package ru.micord.ervu.kafka.controller;
import java.lang.invoke.MethodHandles;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import ervu.client.fileupload.WebDavClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
@ -22,6 +25,7 @@ import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
*/
@RestController
public class ErvuKafkaController {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Autowired
private ReplyingKafkaService replyingKafkaService;
@ -56,7 +60,14 @@ public class ErvuKafkaController {
objectMapper.writeValueAsString(data)
);
ExcerptResponse excerptResponse = objectMapper.readValue(kafkaResponse, ExcerptResponse.class);
return webDavClient.webDavDownloadFile(excerptResponse.getFileUrl());
if (!excerptResponse.getSuccess()) {
throw new RuntimeException("Error with getting excerpt url " + excerptResponse.getMessage());
}
else if (excerptResponse.getData() == null || excerptResponse.getData().getFileUrl() == null
|| excerptResponse.getData().getFileUrl().isEmpty()) {
return ResponseEntity.noContent().build();
}
return webDavClient.webDavDownloadFile(excerptResponse.getData().getFileUrl());
}
catch (Exception e) {
throw new RuntimeException(e);

View file

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

View file

@ -9,24 +9,31 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ExcerptResponse implements Serializable {
private static final long serialVersionUID = 1L;
private boolean success;
private String message;
private ExcerptData data;
private String orgId;
private String fileUrl;
public String getOrgId() {
return orgId;
public boolean getSuccess() {
return success;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
public void setSuccess(boolean success) {
this.success = success;
}
public String getFileUrl() {
return fileUrl;
public String getMessage() {
return message;
}
public void setFileUrl(String fileUrl) {
this.fileUrl = fileUrl;
public void setMessage(String message) {
this.message = message;
}
public ExcerptData getData() {
return data;
}
public void setData(ExcerptData data) {
this.data = data;
}
}

View file

@ -13,6 +13,7 @@ import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
@ -49,31 +50,22 @@ public class SecurityConfig {
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
httpConfigure(http);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(new RequestContextFilter(), LogoutFilter.class);
http.addFilterAfter(filterChainExceptionHandler, RequestContextFilter.class);
return http.build();
}
protected void httpConfigure(HttpSecurity httpSecurity) throws Exception {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepository.setCookieName(TokenConstants.CSRF_TOKEN_NAME);
tokenRepository.setHeaderName(TokenConstants.CSRF_HEADER_NAME);
tokenRepository.setCookiePath("/");
public SecurityFilterChain filterChain(HttpSecurity http,
CookieCsrfTokenRepository tokenRepository)
throws Exception {
XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
delegate.setCsrfRequestAttributeName(null);
// Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
// default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
CsrfTokenRequestHandler requestHandler = delegate::handle;
httpSecurity.authorizeHttpRequests(
http.authorizeHttpRequests(
(authorizeHttpRequests) -> authorizeHttpRequests.requestMatchers(PERMIT_ALL)
.permitAll()
.anyRequest()
.authenticated())
.csrf((csrf) -> csrf.csrfTokenRepository(tokenRepository)
.csrfTokenRequestHandler(requestHandler))
.csrfTokenRequestHandler(requestHandler)
.sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy()))
.logout((logout) -> logout.logoutUrl(ESIA_LOGOUT)
.logoutSuccessHandler(new LogoutSuccessHandler(tokenRepository, esiaAuthService)))
.exceptionHandling()
@ -81,6 +73,19 @@ public class SecurityConfig {
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(new RequestContextFilter(), LogoutFilter.class);
http.addFilterAfter(filterChainExceptionHandler, RequestContextFilter.class);
return http.build();
}
@Bean
public CookieCsrfTokenRepository cookieCsrfTokenRepository() {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepository.setCookieName(TokenConstants.CSRF_TOKEN_NAME);
tokenRepository.setHeaderName(TokenConstants.CSRF_HEADER_NAME);
tokenRepository.setCookiePath("/");
return tokenRepository;
}
public AuthenticationEntryPoint entryPoint() {

View file

@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import ervu.service.okopf.OkopfService;
import org.springframework.security.authentication.AuthenticationManager;
import ru.micord.ervu.security.esia.token.TokensStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -50,7 +51,6 @@ import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication;
import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import ru.micord.ervu.security.webbpm.jwt.model.Token;
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
/**
* @author Eduard Tihomirov
@ -72,6 +72,8 @@ public class EsiaAuthService {
private OkopfService okopfService;
@Autowired
private SecurityHelper securityHelper;
@Autowired
private AuthenticationManager authenticationManager;
@Value("${ervu.kafka.org.reply.topic}")
private String requestReplyTopic;
@ -211,16 +213,16 @@ public class EsiaAuthService {
if (tokenResponse.getError() != null) {
throw new RuntimeException(tokenResponse.getError_description());
}
String accessToken = tokenResponse.getAccess_token();
String esiaAccessTokenStr = tokenResponse.getAccess_token();
String esiaRefreshTokenStr = tokenResponse.getRefresh_token();
boolean hasRole = ulDataService.checkRole(accessToken);
EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken);
boolean hasRole = ulDataService.checkRole(esiaAccessTokenStr);
EsiaAccessToken esiaAccessToken = ulDataService.readToken(esiaAccessTokenStr);
String prnOid = esiaAccessToken.getSbj_id();
String refreshToken = tokenResponse.getRefresh_token();
String ervuId = getErvuId(accessToken, prnOid);
String ervuId = getErvuId(esiaAccessTokenStr, prnOid);
Long expiresIn = tokenResponse.getExpires_in();
TokensStore.addAccessToken(prnOid, accessToken, expiresIn);
TokensStore.addRefreshToken(prnOid, refreshToken, expiresIn);
TokensStore.addAccessToken(prnOid, esiaAccessTokenStr, expiresIn);
TokensStore.addRefreshToken(prnOid, esiaRefreshTokenStr, expiresIn);
Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, hasRole);
int expiry = tokenResponse.getExpires_in().intValue();
Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry);
@ -228,9 +230,10 @@ public class EsiaAuthService {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null);
SecurityContext context = SecurityContextHolder.createEmptyContext();
JwtAuthentication authentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
JwtAuthentication jwtAuthentication = new JwtAuthentication(usernamePasswordAuthenticationToken,
esiaAccessToken.getSbj_id(), token.getValue());
context.setAuthentication(authentication);
authenticationManager.authenticate(jwtAuthentication);
context.setAuthentication(jwtAuthentication);
SecurityContextHolder.setContext(context);
Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry);
response.addCookie(authMarkerCookie);

View file

@ -245,8 +245,10 @@ public class UlDataServiceImpl implements UlDataService {
JsonNode elementsNode = rootNode.path("elements");
StringBuilder names = new StringBuilder();
for (JsonNode element : elementsNode) {
String name = element.path("name").asText();
names.append(name).append("\n");
if (element.path("itSystem").asText().equals(esiaConfig.getClientId())) {
String name = element.path("name").asText();
names.append(name).append("\n");
}
}
return names.toString();
}

View file

@ -0,0 +1,72 @@
package ru.micord.ervu.security.listener;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;
@Component
public class JwtUpdateListener implements ApplicationListener<AuthenticationSuccessEvent> {
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final CsrfTokenRepository tokenRepository;
private final Set<AntPathRequestMatcher> csrfUpdateRequiredPathMatchers;
private CsrfTokenRequestHandler requestHandler = new CsrfTokenRequestAttributeHandler();
@Autowired
public JwtUpdateListener(CsrfTokenRepository tokenRepository) {
Assert.notNull(tokenRepository, "tokenRepository cannot be null");
this.tokenRepository = tokenRepository;
this.csrfUpdateRequiredPathMatchers = Arrays.stream(new String[] {"/esia/auth"})
.map(AntPathRequestMatcher::new)
.collect(Collectors.toSet());
}
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) Objects.requireNonNull(requestAttributes)
.resolveReference(
REFERENCE_REQUEST);
HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
//if csrf cookie update is not required return
if (this.csrfUpdateRequiredPathMatchers.stream()
.noneMatch(matcher -> matcher.matches(request))) {
return;
}
boolean containsToken = this.tokenRepository.loadToken(request) != null;
if (containsToken) {
this.tokenRepository.saveToken(null, request, response);
DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request,
response
);
this.requestHandler.handle(request, response, deferredCsrfToken::get);
this.logger.debug("Replaced CSRF Token");
}
}
}

View file

@ -51,7 +51,7 @@
.webbpm.ervu_lkrp_ul .title {
font-size: var(--size-text-title);
font-family: 'InterSB';
font-family: 'InterB';
padding-top: 0;
padding-bottom: var(--indent-medium);
}
@ -279,6 +279,10 @@
white-space: nowrap;
}
.webbpm.ervu_lkrp_ul .data-group .description {
color: var(--color-text-secondary);
}
.webbpm.ervu_lkrp_ul .pin + .pin {
margin-top: 12px;
}
@ -922,3 +926,13 @@
.webbpm.ervu_lkrp_ul .dialog-link {
cursor: pointer;
}
.webbpm.ervu_lkrp_ul #mydata .data-group:first-child .subtitle {
margin-bottom: 0;
}
.webbpm.ervu_lkrp_ul #mydata .right-block field-set:first-child .fieldset {
padding-top: 24px;
}
.webbpm.ervu_lkrp_ul #mydata .right-block field-set:first-child .fieldset::before {
display: none;
}

View file

@ -1,4 +1,4 @@
class EsiaErrorDetail {
export class EsiaErrorDetail {
private static errors: { [code: string]: string } = {
'ESIA-007071': 'Запрос персональных данных по физическим лицам может быть выполнен только с указанием согласий',
'ESIA-007055': 'Вход в систему осуществляется с неподтвержденной учетной записью',
@ -6,7 +6,7 @@ class EsiaErrorDetail {
'ESIA-007008': 'Сервис авторизации в настоящее время не может выполнить запрос из-за большой нагрузки или технических работ на сервере',
};
static getDescription(code: string): string {
public static getDescription(code: string): string {
return this.errors[code] || 'Доступ запрещен. Обратитесь к системному администратору. Ошибка ' + code;
}
}

View file

@ -4,6 +4,7 @@ import {Observable} from "rxjs";
import {HttpClient, HttpParams} from "@angular/common/http";
import {MessagesService} from "@webbpm/base-package";
import {AuthenticationService} from "../authentication.service";
import {EsiaErrorDetail} from "../EsiaErrorDetail";
@Injectable({providedIn:'root'})
export abstract class AuthGuard implements CanActivate {

View file

@ -288,7 +288,7 @@
<value>
<item id="7a2b1d18-0ea1-49c8-b6bf-5d91761ca0a0" removed="false">
<value>
<simple>"font-bold"</simple>
<simple>"subtitle"</simple>
</value>
</item>
</value>
@ -338,7 +338,7 @@
<value>
<item id="7a2b1d18-0ea1-49c8-b6bf-5d91761ca0a0" removed="false">
<value>
<simple>"font-bold"</simple>
<simple>"description"</simple>
</value>
</item>
</value>
@ -432,16 +432,7 @@
<name>HB</name>
<container>true</container>
<childrenReordered>false</childrenReordered>
<scripts id="bf098f19-480e-44e4-9084-aa42955c4d0f">
<properties>
<entry>
<key>visible</key>
<value>
<simple>false</simple>
</value>
</entry>
</properties>
</scripts>
<scripts id="bf098f19-480e-44e4-9084-aa42955c4d0f"/>
<scripts id="b6068710-0f31-48ec-8e03-c0c1480a40c0"/>
<scripts id="fe04d7fb-6c5b-46c4-b723-667732d81f4f"/>
<scripts id="5c566210-2a60-4048-a2d1-84c7dd023248"/>
@ -610,7 +601,7 @@
<value>
<item id="ddf92f21-aac9-4378-b2e7-3884fa0b1164" removed="false">
<value>
<simple>"font-bold"</simple>
<simple>"subtitle"</simple>
</value>
</item>
</value>
@ -1161,7 +1152,7 @@
<value>
<item id="ddf92f21-aac9-4378-b2e7-3884fa0b1164" removed="false">
<value>
<simple>"font-bold"</simple>
<simple>"subtitle"</simple>
</value>
</item>
</value>