diff --git a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java index 35b65edf..ec294912 100644 --- a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java +++ b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java @@ -50,12 +50,11 @@ import org.w3c.dom.Document; import org.w3c.dom.Node; import ru.micord.ervu.audit.service.AuditService; import ru.micord.ervu.exception.JsonParsingException; +import ru.micord.ervu.journal.SenderInfo; import ru.micord.ervu.kafka.service.ReplyingKafkaService; import ru.micord.ervu.security.esia.config.EsiaConfig; -import ru.micord.ervu.security.esia.model.EmployeeModel; -import ru.micord.ervu.security.esia.model.MchdInfoModel; -import ru.micord.ervu.security.esia.model.PersonModel; -import ru.micord.ervu.security.esia.model.VerifyDocumentSignResponse; +import ru.micord.ervu.security.esia.model.*; +import ru.micord.ervu.security.esia.service.MchdService; import ru.micord.ervu.security.esia.service.UlDataService; import ru.micord.ervu.security.esia.EsiaAuthInfoStore; import ru.micord.ervu.security.webbpm.jwt.UserIdsPair; @@ -91,6 +90,7 @@ public class EmployeeInfoFileUploadService { private final KafkaTemplate kafkaTemplate; private final InteractionService interactionService; private final UlDataService ulDataService; + private final MchdService mchdService; private final AuditService auditService; private final ObjectMapper objectMapper; private final EsiaConfig esiaConfig; @@ -116,7 +116,8 @@ public class EmployeeInfoFileUploadService { AuditService auditService, ObjectMapper objectMapper, EsiaConfig esiaConfig, - @Qualifier("fileTemplate") KafkaTemplate kafkaTemplate) { + @Qualifier("fileTemplate") KafkaTemplate kafkaTemplate, + MchdService mchdService) { this.webDavClient = webDavClient; this.employeeInfoKafkaMessageService = employeeInfoKafkaMessageService; this.replyingKafkaService = replyingKafkaService; @@ -126,6 +127,7 @@ public class EmployeeInfoFileUploadService { this.objectMapper = objectMapper; this.esiaConfig = esiaConfig; this.kafkaTemplate = kafkaTemplate; + this.mchdService = mchdService; } public boolean saveEmployeeInformationFiles(MultipartFile multipartFile, @@ -150,10 +152,14 @@ public class EmployeeInfoFileUploadService { String fio = convertToFio(personModel.getFirstName(), personModel.getMiddleName(), personModel.getLastName() ); - - UploadOrgInfo uploadOrgInfo = employeeInfoKafkaMessageService.getOrgInfo(accessToken, ervuId, - esiaUserId, personModel - ); + OrganizationModel organizationModel = ulDataService.getOrganizationModel(accessToken); + SenderInfo senderInfo = new SenderInfo(); + senderInfo.setFirstName(personModel.getFirstName()); + senderInfo.setLastName(personModel.getLastName()); + senderInfo.setMiddleName(personModel.getMiddleName()); + senderInfo.setPrnOid(esiaUserId); + UploadOrgInfo uploadOrgInfo = new UploadOrgInfo(organizationModel.getFullName(), ervuId, senderInfo, + organizationModel.getOid()); EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(formType); @@ -251,7 +257,7 @@ public class EmployeeInfoFileUploadService { } return validateSignerAndMchd(verifyDocumentSignResponse, chiefModel, uploadOrgInfo, - mchdFile, accessToken, fileInfo, signFileInfo, mchdFileInfo, ervuId, response + mchdFile, fileInfo, signFileInfo, mchdFileInfo, ervuId, response, organizationModel.getOgrn() ); } @@ -321,9 +327,8 @@ public class EmployeeInfoFileUploadService { } private boolean validateSignerAndMchd(VerifyDocumentSignResponse verifyDocumentSignResponse, - EmployeeModel chiefModel, UploadOrgInfo uploadOrgInfo, MultipartFile mchdFile, - String accessToken, FileInfo fileInfo, FileInfo signFileInfo, - FileInfo mchdFileInfo, String ervuId, String response) { + EmployeeModel chiefModel, UploadOrgInfo uploadOrgInfo, MultipartFile mchdFile, FileInfo fileInfo, FileInfo signFileInfo, + FileInfo mchdFileInfo, String ervuId, String response, String ogrn) { String signerInfo = verifyDocumentSignResponse.getSignerInfo(); Map signerInfoMap = parseKeyValuePairs(signerInfo); @@ -367,9 +372,7 @@ public class EmployeeInfoFileUploadService { new FileInfo[] {fileInfo, signFileInfo, mchdFileInfo}, fileInfo.getFileStatus() ); try { - validateMchd(mchdFile, accessToken, signerInfoMap.get("SN") + " " + signerInfoMap.get("G"), - chiefFirstName, chiefLastName, chiefMiddleName - ); + validateMchd(mchdFile, ogrn, signerInfoMap.get("СНИЛС")); if (sendMessage(fileStatusResponse)) { interactionService.updateStatus(fileInfo.getFileId(), "Направлено в ЕРВУ", ervuId); @@ -537,8 +540,7 @@ public class EmployeeInfoFileUploadService { } } - private void validateMchd(MultipartFile mchdFile, String accessToken, String agentFio, - String chiefFirstName, String chiefLastName, String chiefMiddleName) { + private void validateMchd(MultipartFile mchdFile, String ogrn, String snils) { String mchdGuid; try { mchdGuid = getMchdGuid(mchdFile); @@ -546,31 +548,27 @@ public class EmployeeInfoFileUploadService { catch (Exception e) { throw new LocalizedException("mchd_cant_parse", MESSAGE_SOURCE); } - MchdInfoModel mchdInfoModel = ulDataService.getMchdInfoModel(mchdGuid, accessToken); + MchdInfoModel mchdInfoModel = mchdService.getMchdInfoModel(mchdGuid); if (!mchdInfoModel.getStatus().equals("A")) { throw new LocalizedException("mchd_expired", MESSAGE_SOURCE); } boolean validAgent = mchdInfoModel.getAgents() .getElements() .stream() - .anyMatch(agent1 -> agentFio.equalsIgnoreCase( - agent1.getPerson().getLastName() + " " + agent1.getPerson().getFirstName() + " " - + agent1.getPerson().getMiddleName())); + .anyMatch(agent1 -> agent1.getPerson().getSnils().equals(snils)); if (!validAgent) { throw new LocalizedException("mchd_validate_agent", MESSAGE_SOURCE); } while (mchdInfoModel.getParentGuid() != null) { - mchdInfoModel = ulDataService.getMchdInfoModel(mchdInfoModel.getParentGuid(), accessToken); + mchdInfoModel = mchdService.getMchdInfoModel(mchdInfoModel.getParentGuid()); if (!mchdInfoModel.getStatus().equals("A")) { throw new LocalizedException("mchd_tree_expired", MESSAGE_SOURCE); } } MchdInfoModel.Element principal = mchdInfoModel.getPrincipals().getElements().get(0); - MchdInfoModel.Person chief = principal.getOrganization().getChief(); - boolean principalFioEquals = chief.getFirstName().equalsIgnoreCase(chiefFirstName) && - chief.getLastName().equalsIgnoreCase(chiefLastName) && - chief.getMiddleName().equalsIgnoreCase(chiefMiddleName); - if (!principalFioEquals) { + String ogrnMCHD = principal.getOrganization().getOgrn(); + boolean ogrnEqueals = ogrnMCHD.equals(ogrn); + if (!ogrnEqueals) { throw new LocalizedException("mchd_validate_principal", MESSAGE_SOURCE); } } diff --git a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoKafkaMessageService.java b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoKafkaMessageService.java index 4e6b86bd..0b12e317 100644 --- a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoKafkaMessageService.java +++ b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoKafkaMessageService.java @@ -23,28 +23,6 @@ public class EmployeeInfoKafkaMessageService { this.ulDataService = ulDataService; } - public EmployeeInfoKafkaMessage getKafkaMessage(String fileId, String fileUrl, String fileName, - EmployeeInfoFileFormType formType, String departureDateTime, String accessToken, - String offset, FileStatus fileStatus, String ervuId, String prnOid, - PersonModel personModel, long fileSize, String type) { - return new EmployeeInfoKafkaMessage( - getOrgInfo(accessToken, ervuId, prnOid, personModel), - new FileInfo[] { - getFileInfo( - fileId, - fileUrl, - fileName, - formType, - departureDateTime, - offset, - fileStatus, - fileSize, - type - ), - } - ); - } - public EmployeeInfoKafkaMessage getKafkaMessage(UploadOrgInfo orgInfo, FileInfo[] filesInfo) { return new EmployeeInfoKafkaMessage(orgInfo, filesInfo); } @@ -65,15 +43,4 @@ public class EmployeeInfoKafkaMessageService { type ); } - - public UploadOrgInfo getOrgInfo(String accessToken, String ervuId, String prnOid, PersonModel personModel) { - OrganizationModel organizationModel = ulDataService.getOrganizationModel(accessToken); - SenderInfo senderInfo = new SenderInfo(); - senderInfo.setFirstName(personModel.getFirstName()); - senderInfo.setLastName(personModel.getLastName()); - senderInfo.setMiddleName(personModel.getMiddleName()); - senderInfo.setPrnOid(prnOid); - return new UploadOrgInfo(organizationModel.getFullName(), ervuId, senderInfo, - organizationModel.getOid()); - } } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/EsiaAuthInfoStore.java b/backend/src/main/java/ru/micord/ervu/security/esia/EsiaAuthInfoStore.java index 7b813a15..3c3b6cf5 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/EsiaAuthInfoStore.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/EsiaAuthInfoStore.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,6 +28,7 @@ public class EsiaAuthInfoStore { private static final Map> PRNS_UUID_STATE_MAP = new ConcurrentHashMap<>(); private static final MessageSourceAccessor MESSAGE_SOURCE = MessageBundleUtils.createAccessor( "messages/common_errors_messages"); + private static final AtomicReference SYSTEM_ESIA_TOKEN = new AtomicReference<>(); public static void addAccessToken(String prnOid, String token, long expiresIn) { if (token != null) { @@ -152,4 +154,20 @@ public class EsiaAuthInfoStore { }); } } + + public static void setSystemEsiaToken(String token, Long expiresIn) { + if (token != null) { + long expiryTime = System.currentTimeMillis() + 1000L * expiresIn; + SYSTEM_ESIA_TOKEN.set(new ExpiringToken(token, expiryTime)); + } + } + + public static String getSystemEsiaToken() { + ExpiringToken expiringToken = SYSTEM_ESIA_TOKEN.get(); + if (expiringToken == null || expiringToken.getAccessToken() == null + || expiringToken.isExpired()) { + return null; + } + return expiringToken.getAccessToken(); + } } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/config/EsiaConfig.java b/backend/src/main/java/ru/micord/ervu/security/esia/config/EsiaConfig.java index decebcdb..a8c58d73 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/config/EsiaConfig.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/config/EsiaConfig.java @@ -74,6 +74,12 @@ public class EsiaConfig { @Value("${file.sign.verify.url}") private String fileSignVerifyUrl; + @Value("${esia.system.scope}") + private String esiaSystemScope; + + @Value("${esia.system.token.url}") + private String esiaSystemTokenUrl; + public String getEsiaOrgScopes() { String[] scopeItems = esiaOrgScopes.split(","); return String.join(" ", Arrays.stream(scopeItems).map(item -> orgScopeUrl + item.trim()).toArray(String[]::new)); @@ -153,4 +159,12 @@ public class EsiaConfig { public String getFileSignVerifyUrl() { return fileSignVerifyUrl; } + + public String getEsiaSystemScope() { + return esiaSystemScope; + } + + public String getEsiaSystemTokenUrl() { + return esiaSystemTokenUrl; + } } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/model/MchdInfoModel.java b/backend/src/main/java/ru/micord/ervu/security/esia/model/MchdInfoModel.java index b2f25961..ab63ee75 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/model/MchdInfoModel.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/model/MchdInfoModel.java @@ -95,54 +95,27 @@ public class MchdInfoModel implements Serializable { @JsonIgnoreProperties(ignoreUnknown = true) public static class Organization implements Serializable { - private String name; - private Person chief; + private String ogrn; - public String getName() { - return name; + public String getOgrn() { + return ogrn; } - public void setName(String name) { - this.name = name; - } - - public Person getChief() { - return chief; - } - - public void setChief(Person chief) { - this.chief = chief; + public void setOgrn(String ogrn) { + this.ogrn = ogrn; } } @JsonIgnoreProperties(ignoreUnknown = true) public static class Person implements Serializable { - private String firstName; - private String lastName; - private String middleName; + private String snils; - public String getFirstName() { - return firstName; + public String getSnils() { + return snils; } - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getMiddleName() { - return middleName; - } - - public void setMiddleName(String middleName) { - this.middleName = middleName; + public void setSnils(String snils) { + this.snils = snils; } } } 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 a9f403db..564c9058 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 @@ -655,4 +655,73 @@ public class EsiaAuthService { throw new EsiaException("Unknown error in verify module. Error code " + errorCode); } } + + public String getSystemAccessToken() { + String esiaAccessTokenStr = null; + Long expiresIn = null; + try { + String clientId = esiaConfig.getClientId(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx"); + ZonedDateTime dt = ZonedDateTime.now(); + String timestamp = dt.format(formatter); + String scope = esiaConfig.getEsiaSystemScope(); + + Map parameters = new LinkedHashMap(); + parameters.put("client_id", clientId); + parameters.put("scope", scope); + parameters.put("timestamp", timestamp); + parameters.put("state", "%s"); + parameters.put("redirect_uri", esiaConfig.getRedirectUrl()); + + SignResponse signResponse = signMap(parameters); + String newState = signResponse.getState(); + String clientSecret = signResponse.getSignature(); + String authUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaSystemTokenUrl(); + String postBody = new FormUrlencoded() + .setParameter("client_id", clientId) + .setParameter("grant_type", "client_credentials") + .setParameter("client_secret", clientSecret) + .setParameter("state", newState) + .setParameter("scope", scope) + .setParameter("timestamp", timestamp) + .setParameter("token_type", "Bearer") + .setParameter("redirect_uri", esiaConfig.getRedirectUrl()) + .toFormUrlencodedString(); + HttpRequest postReq = HttpRequest.newBuilder(URI.create(authUrl)) + .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded") + .POST(HttpRequest.BodyPublishers.ofString(postBody)) + .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout())) + .build(); + HttpResponse postResp = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout())) + + .build() + .send(postReq, HttpResponse.BodyHandlers.ofString()); + String responseString = postResp.body(); + EsiaTokenResponse tokenResponse = objectMapper.readValue(responseString, + EsiaTokenResponse.class + ); + + if (tokenResponse == null || tokenResponse.getError() != null) { + String errMsg = + tokenResponse != null ? tokenResponse.getErrorDescription() : "response is empty"; + throw new IllegalStateException("Esia response error. " + errMsg); + } + if (!tokenResponse.getState().equals(newState)) { + throw new EsiaException( + "Token invalid. State from request not equals with state from response."); + } + esiaAccessTokenStr = tokenResponse.getAccessToken(); + String verifyResult = verifyToken(esiaAccessTokenStr); + if (verifyResult != null) { + throw new EsiaException(verifyResult); + } + expiresIn = tokenResponse.getExpiresIn(); + EsiaAuthInfoStore.setSystemEsiaToken(esiaAccessTokenStr, expiresIn); + return esiaAccessTokenStr; + } + catch (Exception e) { + throw new EsiaException(e); + } + } } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/MchdService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/MchdService.java new file mode 100644 index 00000000..2fd30282 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/MchdService.java @@ -0,0 +1,73 @@ +package ru.micord.ervu.security.esia.service; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Service; +import ru.micord.ervu.security.esia.EsiaAuthInfoStore; +import ru.micord.ervu.security.esia.config.EsiaConfig; +import ru.micord.ervu.security.esia.exception.EsiaException; +import ru.micord.ervu.security.esia.model.MchdInfoModel; + +/** + * @author Eduard Tihomirov + */ +@Service +public class MchdService { + + @Autowired + private EsiaConfig esiaConfig; + + @Autowired + private ObjectMapper objectMapper; + @Autowired + private EsiaAuthService esiaAuthService; + + public MchdInfoModel getMchdInfoModel(String guid) { + String accessToken = EsiaAuthInfoStore.getSystemEsiaToken(); + boolean isNewToken = (accessToken == null); + if (isNewToken) { + accessToken = esiaAuthService.getSystemAccessToken(); + } + try { + HttpResponse response = executeRequest(accessToken, guid); + + if (response.statusCode() != 200) { + if (isNewToken) { + throw new EsiaException(response.statusCode() + " " + response.body()); + } + accessToken = esiaAuthService.getSystemAccessToken(); + response = executeRequest(accessToken, guid); + + if (response.statusCode() != 200) { + throw new EsiaException(response.statusCode() + " " + response.body()); + } + } + + return objectMapper.readValue(response.body(), MchdInfoModel.class); + } + catch (Exception e) { + throw new EsiaException(e); + } + } + + private HttpResponse executeRequest(String accessToken, String guid) throws Exception { + String url = esiaConfig.getEsiaBaseUri() + "/poa-registry/api/public/v1/poa/" + guid; + HttpRequest getReq = HttpRequest.newBuilder(URI.create(url)) + .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded") + .header("Authorization", "Bearer ".concat(accessToken)) + .GET() + .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout())) + .build(); + return HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout())) + .build() + .send(getReq, HttpResponse.BodyHandlers.ofString()); + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/UlDataService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/UlDataService.java index 8d5aabe5..8a5cee16 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/UlDataService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/UlDataService.java @@ -22,6 +22,4 @@ public interface UlDataService { String getAllUserRoles(String accessToken); EsiaHeader readHeader(String accessToken); - - MchdInfoModel getMchdInfoModel(String guid, String accessToken); } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/UlDataServiceImpl.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/UlDataServiceImpl.java index d4214a6b..51da22e5 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/UlDataServiceImpl.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/UlDataServiceImpl.java @@ -20,7 +20,6 @@ import ru.micord.ervu.security.esia.model.BrhsModel; import ru.micord.ervu.security.esia.model.EmployeeModel; import ru.micord.ervu.security.esia.model.EsiaAccessToken; import ru.micord.ervu.security.esia.model.EsiaHeader; -import ru.micord.ervu.security.esia.model.MchdInfoModel; import ru.micord.ervu.security.esia.model.OrganizationModel; import ru.micord.ervu.security.esia.model.PersonModel; @@ -282,25 +281,4 @@ public class UlDataServiceImpl implements UlDataService { throw new EsiaException(e); } } - - public MchdInfoModel getMchdInfoModel(String guid, String accessToken) { - try { - String url = esiaConfig.getEsiaBaseUri() + "/poa-registry/api/public/v1/poa/" + guid; - HttpRequest getReq = HttpRequest.newBuilder(URI.create(url)) - .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded") - .header("Authorization", "Bearer ".concat(accessToken)) - .GET() - .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout())) - .build(); - HttpResponse getResp = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout())) - .build() - .send(getReq, HttpResponse.BodyHandlers.ofString()); - errorHandler(getResp); - return objectMapper.readValue(getResp.body(), MchdInfoModel.class); - } - catch (Exception e) { - throw new EsiaException(e); - } - } }