From 8120afbcf4b093ea14ef571f54a417fdf1a01c1e Mon Sep 17 00:00:00 2001 From: Eduard Tihomirov Date: Fri, 18 Oct 2024 11:41:42 +0300 Subject: [PATCH] SUPPORT-8601, SUPPORT-8602, SUPPORT-8613, SUPPORT-8599 --- .../java/ru/micord/ervu/s3/S3Service.java | 3 ++ .../esia/controller/EsiaController.java | 3 +- .../esia/service/EsiaAuthService.java | 25 +++++++++---- .../esia/service/EsiaDataService.java | 10 +++--- .../button/ErvuDownloadFileButton.ts | 36 ++++++++++++++++++- .../ts/modules/security/EsiaErrorDetail.ts | 12 +++++++ .../ts/modules/security/guard/auth.guard.ts | 31 +++++++++++++--- .../business-model/Журнал взаимодействия.page | 22 ++++++++++-- 8 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 frontend/src/ts/modules/security/EsiaErrorDetail.ts diff --git a/backend/src/main/java/ru/micord/ervu/s3/S3Service.java b/backend/src/main/java/ru/micord/ervu/s3/S3Service.java index fce537a0..509de3e8 100644 --- a/backend/src/main/java/ru/micord/ervu/s3/S3Service.java +++ b/backend/src/main/java/ru/micord/ervu/s3/S3Service.java @@ -28,6 +28,9 @@ public class S3Service { public ResponseEntity getFile(String fileUrl) { try { + if (fileUrl == null || fileUrl.isEmpty()) { + return ResponseEntity.noContent().build(); + } AmazonS3URI uri = new AmazonS3URI(fileUrl); S3Object s3Object = outClient.getObject(uri.getBucket(), uri.getKey()); InputStreamResource resource = new InputStreamResource(s3Object.getObjectContent()); diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java b/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java index d408ff4d..abbc0932 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/controller/EsiaController.java @@ -3,6 +3,7 @@ package ru.micord.ervu.security.esia.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.http.ResponseEntity; import ru.micord.ervu.security.esia.model.OrgInfoModel; import ru.micord.ervu.security.esia.service.EsiaAuthService; import ru.micord.ervu.security.esia.service.EsiaDataService; @@ -30,7 +31,7 @@ public class EsiaController { } @RequestMapping(value = "/esia/auth", params = "code", method = RequestMethod.GET) - public boolean esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) { + public ResponseEntity esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) { return esiaAuthService.getEsiaTokensByCode(code, request, response); } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 5950ce20..5858c626 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -1,6 +1,7 @@ package ru.micord.ervu.security.esia.service; import java.io.UnsupportedEncodingException; +import java.lang.invoke.MethodHandles; import java.net.URI; import java.net.URL; import java.net.URLEncoder; @@ -21,6 +22,10 @@ import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; import ervu.service.okopf.OkopfService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContext; import org.springframework.util.StringUtils; import ru.micord.ervu.security.esia.config.EsiaConfig; @@ -50,6 +55,8 @@ import ru.micord.ervu.security.webbpm.jwt.model.Token; @Service public class EsiaAuthService { + private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + @Value("${cookie-path:#{null}}") private String path; @@ -154,7 +161,7 @@ public class EsiaAuthService { return uriBuilder.toString(); } - public boolean getEsiaTokensByCode(String esiaAuthCode, HttpServletRequest request, HttpServletResponse response) { + public ResponseEntity getEsiaTokensByCode(String esiaAuthCode, HttpServletRequest request, HttpServletResponse response) { try { String clientId = esiaConfig.getClientId(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx"); @@ -205,8 +212,14 @@ public class EsiaAuthService { } String accessToken = tokenResponse.getAccess_token(); boolean hasRole = ulDataService.checkRole(accessToken); + EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); + String prnOid = esiaAccessToken.getSbj_id(); if (!hasRole) { - throw new RuntimeException("The user does not have the required role"); + LOGGER.error("The user with id = " + prnOid + " does not have the required role"); + return new ResponseEntity<>( + "Доступ запрещен. Пользователь должен быть включен в группу \"Сотрудник, ответственный за военно-учетную работу\" в ЕСИА", + HttpStatus.FORBIDDEN + ); } String cookiePath = null; if (path != null) { @@ -225,8 +238,6 @@ public class EsiaAuthService { cookieRefresh.setHttpOnly(true); cookieRefresh.setPath(cookiePath); response.addCookie(cookieRefresh); - - EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); String ervuId = getErvuId(accessToken, esiaAccessToken.getSbj_id()); Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), tokenResponse.getExpires_in(), ervuId); Cookie authToken = new Cookie("auth_token", token.getValue()); @@ -245,7 +256,7 @@ public class EsiaAuthService { isAuth.setMaxAge(tokenResponse.getExpires_in().intValue()); isAuth.setPath("/"); response.addCookie(isAuth); - return true; + return ResponseEntity.ok("Authentication successful"); } catch (Exception e) { throw new RuntimeException(e); @@ -442,7 +453,9 @@ public class EsiaAuthService { private OrgInfo copyToOrgInfo(OrganizationModel organizationModel, EmployeeModel employeeModel, EmployeeModel chiefModel ) { OrgInfo orgInfo = new OrgInfo(); - orgInfo.setChiefInfo(copyToEmployee(chiefModel)); + if (chiefModel != null) { + orgInfo.setChiefInfo(copyToEmployee(chiefModel)); + } orgInfo.setSenderInfo(copyToEmployee(employeeModel)); orgInfo.setBrhs( Arrays.stream(organizationModel.getBrhs()).map(brhsModel -> { diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java index 8eac1efc..537d99ac 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaDataService.java @@ -44,10 +44,12 @@ public class EsiaDataService { } } ); } - orgInfoModel.chiefFullname = - chiefEmployeeModel.getPerson().getLastName() + " " + chiefEmployeeModel.getPerson() - .getFirstName() + " " + chiefEmployeeModel.getPerson().getMiddleName(); - orgInfoModel.chiefPosition = chiefEmployeeModel.getPosition(); + if (chiefEmployeeModel != null) { + orgInfoModel.chiefFullname = + chiefEmployeeModel.getPerson().getLastName() + " " + chiefEmployeeModel.getPerson() + .getFirstName() + " " + chiefEmployeeModel.getPerson().getMiddleName(); + orgInfoModel.chiefPosition = chiefEmployeeModel.getPosition(); + } orgInfoModel.ogrn = organizationModel.getOgrn(); orgInfoModel.kpp = organizationModel.getKpp(); orgInfoModel.inn = organizationModel.getInn(); diff --git a/frontend/src/ts/ervu/component/button/ErvuDownloadFileButton.ts b/frontend/src/ts/ervu/component/button/ErvuDownloadFileButton.ts index 0aae45c6..ed7e8ce4 100644 --- a/frontend/src/ts/ervu/component/button/ErvuDownloadFileButton.ts +++ b/frontend/src/ts/ervu/component/button/ErvuDownloadFileButton.ts @@ -1,6 +1,8 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from "@angular/core"; -import {AbstractButton, NotNull} from "@webbpm/base-package"; +import {AbstractButton, MessagesService, NotNull, ObjectRef} from "@webbpm/base-package"; import {HttpClient} from "@angular/common/http"; +import {InMemoryStaticGrid} from "../grid/InMemoryStaticGrid"; +import {Subscription} from "rxjs"; /** * @author: Eduard Tihomirov @@ -13,10 +15,19 @@ import {HttpClient} from "@angular/common/http"; }) export class ErvuDownloadFileButton extends AbstractButton { private httpClient: HttpClient; + private messageService: MessagesService; + private gridLoadedSubscription: Subscription; + + @ObjectRef() + @NotNull() + public grid: InMemoryStaticGrid; @NotNull() public fileName: string; + @NotNull() + public noFileMessage: string; + constructor(el: ElementRef, cd: ChangeDetectorRef) { super(el, cd); } @@ -24,6 +35,26 @@ export class ErvuDownloadFileButton extends AbstractButton { initialize() { super.initialize(); this.httpClient = this.injector.get(HttpClient); + this.messageService = this.injector.get(MessagesService); + if (this.grid.isInitialized() && this.grid.getRowDataSize() > 0) { + this.setEnabled(true); + this.setVisible(true); + } + this.gridLoadedSubscription = this.grid.gridLoaded.subscribe(() => { + if (this.grid.getRowDataSize() > 0) { + this.setEnabled(true); + this.setVisible(true); + } + else { + this.setEnabled(false); + this.setVisible(false); + } + }); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.gridLoadedSubscription.unsubscribe(); } public doClickActions(): Promise { @@ -31,6 +62,9 @@ export class ErvuDownloadFileButton extends AbstractButton { responseType: 'blob', observe: 'response' }).toPromise().then((response) => { + if (response.status == 204) { + this.messageService.info(this.noFileMessage) + } const contentDisposition = response.headers.get('Content-Disposition'); const url = window.URL.createObjectURL(response.body); const a = document.createElement('a'); diff --git a/frontend/src/ts/modules/security/EsiaErrorDetail.ts b/frontend/src/ts/modules/security/EsiaErrorDetail.ts new file mode 100644 index 00000000..9447f3fc --- /dev/null +++ b/frontend/src/ts/modules/security/EsiaErrorDetail.ts @@ -0,0 +1,12 @@ +class EsiaErrorDetail { + private static errors: { [code: string]: string } = { + 'ESIA-007071': 'Запрос персональных данных по физическим лицам может быть выполнен только с указанием согласий', + 'ESIA-007055': 'Вход в систему осуществляется с неподтвержденной учетной записью', + 'ESIA-007036': 'Учетная запись заблокирована', + 'ESIA-007008': 'Сервис авторизации в настоящее время не может выполнить запрос из-за большой нагрузки или технических работ на сервере', + }; + + static getDescription(code: string): string { + return this.errors[code] || 'Доступ запрещен. Обратитесь к системному администратору. Ошибка ' + code; + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/security/guard/auth.guard.ts b/frontend/src/ts/modules/security/guard/auth.guard.ts index 95c2271e..87201687 100644 --- a/frontend/src/ts/modules/security/guard/auth.guard.ts +++ b/frontend/src/ts/modules/security/guard/auth.guard.ts @@ -30,14 +30,31 @@ export abstract class AuthGuard implements CanActivate { return true; } else if (error) { - let errorMessage = error + ', error description =' + errorDescription; - this.messageService.error(errorMessage) + let userErrorMessage = 'Произошла неизвестная ошибка. Обратитесь к системному администратору'; + let errorCode = this.extractCode(errorDescription); + if (errorCode) { + userErrorMessage = EsiaErrorDetail.getDescription(errorCode); + } + let errorMessage = error + ', error description = ' + errorDescription; + this.messageService.error(userErrorMessage) throw new Error(errorMessage); } else if (code) { const params = new HttpParams().set('code', code); - this.httpClient.get("esia/auth", {params: params}).toPromise().then( - () => window.open(url.origin + url.pathname, "_self")) + this.httpClient.get("esia/auth", + {params: params, responseType: 'text', observe: 'response'}) + .toPromise() + .then( + (response) => { + if (response.status == 200) { + window.open(url.origin + url.pathname, "_self"); + } + else { + let errorMessage = response.body; + this.messageService.error(errorMessage) + throw new Error(errorMessage) + } + }) .catch((reason) => console.error(reason) ); @@ -62,4 +79,10 @@ export abstract class AuthGuard implements CanActivate { public getIsAuth(): string { return this.cookieService.get('webbpm.ervu-lkrp-ul'); } + + private extractCode(message: string): string | null { + const regex = /ESIA-\d{6}/; + const match = message.match(regex); + return match ? match[0] : null; + } } diff --git a/resources/src/main/resources/business-model/Журнал взаимодействия.page b/resources/src/main/resources/business-model/Журнал взаимодействия.page index f030e1fb..f5a77852 100644 --- a/resources/src/main/resources/business-model/Журнал взаимодействия.page +++ b/resources/src/main/resources/business-model/Журнал взаимодействия.page @@ -1286,16 +1286,34 @@ "Запросить выписку" + + disabled + +true + + fileName -"Выписка" +"Выписка.xlsx" + + + + grid + +{"objectId":"bbaf33d7-0679-440b-a394-cb805ce80300","packageName":"ervu.component.grid","className":"InMemoryStaticGrid","type":"TS"} + + + + noFileMessage + +"Нет записей в выписке журнала взаимодействия" visible -true +false