diff --git a/backend/pom.xml b/backend/pom.xml index ef045a0b..230190b3 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -5,12 +5,16 @@ ru.micord.ervu.lkrp ul - 1.9.0-SNAPSHOT + 1.8.2 ru.micord.ervu.lkrp.ul backend war + + org.springframework.security + spring-security-jwt + io.jsonwebtoken jjwt-api @@ -29,10 +33,23 @@ resources runtime + + ru.cg.webbpm.modules.reporting.reporting-jasper + reporting-jasper-fonts + runtime + + + org.ocpsoft.prettytime + prettytime + org.jooq jooq + + org.apache.santuario + xmlsec + javax.servlet javax.servlet-api @@ -122,10 +139,6 @@ database-impl compile - - javax.annotation - javax.annotation-api - ru.cg.webbpm.modules.jndi jndi-beans @@ -134,10 +147,51 @@ ru.cg.webbpm.modules.jndi jndi-inject + + com.sun.mail + javax.mail + + + ru.cg.webbpm.modules.database + database-test + test + ru.cg.webbpm.modules standard-annotations + + ru.cg.webbpm.modules.core + metrics + + + ru.cg.webbpm.modules.reporting + reporting-api + + + ru.cg.webbpm.modules.reporting + reporting-runtime-api + + + ru.cg.webbpm.modules.reporting + reporting-runtime-impl + + + ru.cg.webbpm.modules.reporting.reporting-jasper + reporting-jasper-impl + + + ru.cg.webbpm.modules.reporting.reporting-jasper + reporting-jasper-runtime-impl + + + ru.cg.webbpm.modules.reporting.reporting-xdoc + reporting-xdoc-impl + + + ru.cg.webbpm.modules.reporting.reporting-xdoc + reporting-xdoc-runtime-impl + org.liquibase liquibase-core @@ -146,6 +200,22 @@ ru.cg.webbpm.modules webkit-base + + xerces + xercesImpl + + + com.google.guava + guava + + + ru.micord.fias + client + + + org.apache.tika + tika-core + net.javacrumbs.shedlock shedlock-spring @@ -174,10 +244,6 @@ org.apache.logging.log4j log4j-web - - org.postgresql - postgresql - com.amazonaws aws-java-sdk-s3 diff --git a/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java b/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java index 310a9acd..3ba47c52 100644 --- a/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java +++ b/backend/src/main/java/ervu/controller/EmployeeInfoFileUploadController.java @@ -34,20 +34,17 @@ public class EmployeeInfoFileUploadController { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { - if (cookie.getName().equals("access_token")) { - accessToken = cookie.getValue(); - } - else if (cookie.getName().equals("auth_token")) { + if (cookie.getName().equals("auth_token")) { authToken = cookie.getValue(); } } } - if (accessToken != null) { + if (authToken != null) { String offset = ZonedDateTime.now(TimeZone.getTimeZone(clientTimeZone).toZoneId()) .getOffset().getId(); - if (this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType, accessToken, + if (this.fileUploadService.saveEmployeeInformationFile(multipartFile, formType, authToken, offset)) { return ResponseEntity.ok("File successfully uploaded."); } diff --git a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java index 0de9a1c6..93de694c 100644 --- a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java +++ b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java @@ -24,6 +24,7 @@ import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import ru.micord.ervu.security.esia.token.TokensStore; import ru.micord.ervu.security.esia.model.EmployeeModel; import ru.micord.ervu.security.esia.model.PersonModel; import ru.micord.ervu.security.esia.service.UlDataService; @@ -72,8 +73,7 @@ public class EmployeeInfoFileUploadService { this.jwtTokenService = jwtTokenService; } - public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType, - String accessToken, String authToken, String offset) { + public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType, String authToken, String offset) { String fileUploadUrl = this.url + "/" + getNewFilename(multipartFile.getOriginalFilename()); LocalDateTime now = LocalDateTime.now(); @@ -85,10 +85,13 @@ public class EmployeeInfoFileUploadService { String fileId = UUID.randomUUID().toString(); String fileName = multipartFile.getOriginalFilename(); EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(formType); - EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); - PersonModel personModel = employeeModel.getPerson(); Token token = jwtTokenService.getToken(authToken); String[] ids = token.getUserAccountId().split(":"); + String userId = ids[0]; + String ervuId = ids[1]; + String accessToken = TokensStore.getAccessToken(userId); + EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken); + PersonModel personModel = employeeModel.getPerson(); String departureDateTime = now.format(DateTimeFormatter.ofPattern(FORMAT)); String jsonMessage = getJsonKafkaMessage( employeeInfoKafkaMessageService.getKafkaMessage( @@ -100,15 +103,15 @@ public class EmployeeInfoFileUploadService { accessToken, offset, fileStatus, - ids[1], - ids[0], + ervuId, + userId, personModel ) ); interactionService.setStatus(fileId, fileStatus.getStatus(), fileName, employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now), convertToFio(personModel.getFirstName(), personModel.getMiddleName(), personModel.getLastName()), - (int) multipartFile.getSize(), ids[1]); + (int) multipartFile.getSize(), ervuId); return sendMessage(jsonMessage); } else { diff --git a/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java b/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java new file mode 100644 index 00000000..9993b234 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/LogoutSuccessHandler.java @@ -0,0 +1,34 @@ +package ru.micord.ervu.security; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.CsrfTokenRepository; +import ru.micord.ervu.security.esia.service.EsiaAuthService; + +public class LogoutSuccessHandler + implements org.springframework.security.web.authentication.logout.LogoutSuccessHandler { + + private final CsrfTokenRepository csrfTokenRepository; + private final EsiaAuthService esiaAuthService; + + public LogoutSuccessHandler(CsrfTokenRepository csrfTokenRepository, + EsiaAuthService esiaAuthService) { + this.csrfTokenRepository = csrfTokenRepository; + this.esiaAuthService = esiaAuthService; + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + String url = esiaAuthService.logout(request, response); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write(url); + response.getWriter().flush(); + CsrfToken csrfToken = this.csrfTokenRepository.generateToken(request); + this.csrfTokenRepository.saveToken(csrfToken, request, response); + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java index c9d8e2be..a5aaa4d9 100644 --- a/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConfig.java @@ -4,59 +4,105 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfTokenRequestHandler; +import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; +import org.springframework.web.filter.RequestContextFilter; +import ru.micord.ervu.security.esia.service.EsiaAuthService; +import ru.micord.ervu.security.filter.FilterChainExceptionHandler; +import ru.micord.ervu.security.webbpm.jwt.JwtAuthenticationProvider; +import ru.micord.ervu.security.webbpm.jwt.JwtMatcher; import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint; +import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter; +import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper; +import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; + +import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT; @Configuration @EnableWebSecurity -public class SecurityConfig extends WebSecurityConfigurerAdapter { +public class SecurityConfig { + private static final String[] PERMIT_ALL = new String[] { + "/version", "/esia/url", "/esia/auth", "esia/refresh" + }; + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + @Autowired + private EsiaAuthService esiaAuthService; + @Autowired + private FilterChainExceptionHandler filterChainExceptionHandler; + @Autowired + private JwtAuthenticationProvider jwtAuthenticationProvider; - @Autowired - private JwtAuthenticationFilter jwtAuthenticationFilter; + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) { + auth.authenticationProvider(jwtAuthenticationProvider); + } - @Override - protected void configure(HttpSecurity http) throws Exception { + @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(); + } - httpConfigure(http); - http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - } + protected void httpConfigure(HttpSecurity httpSecurity) throws Exception { + CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); + tokenRepository.setCookiePath("/"); + 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( + (authorizeHttpRequests) -> authorizeHttpRequests.requestMatchers(PERMIT_ALL) + .permitAll() + .anyRequest() + .authenticated()) + .csrf((csrf) -> csrf.csrfTokenRepository(tokenRepository) + .csrfTokenRequestHandler(requestHandler)) + .logout((logout) -> logout.logoutUrl(ESIA_LOGOUT) + .logoutSuccessHandler(new LogoutSuccessHandler(tokenRepository, esiaAuthService))) + .exceptionHandling() + .authenticationEntryPoint(entryPoint()) + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + } - protected void httpConfigure(HttpSecurity httpSecurity) throws Exception { - String[] permitAll = {"/version", "/esia/url", "/esia/auth", "esia/refresh"}; + public AuthenticationEntryPoint entryPoint() { + return new UnauthorizedEntryPoint(); + } - httpSecurity.authorizeRequests() - .antMatchers(permitAll).permitAll() - .antMatchers("/**").authenticated() - .and() - .csrf().disable() - .exceptionHandling().authenticationEntryPoint(entryPoint()) - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS); - } + @Bean + AuthenticationManager authenticationManager( + AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } - public AuthenticationEntryPoint entryPoint() { - return new UnauthorizedEntryPoint(); - } + @Bean + public SecurityHelper securityHelper() { + return new SecurityHelper(); + } - @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - - @Bean - public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception { - JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter("/**", - entryPoint() - ); - jwtAuthenticationFilter.setAuthenticationManager(authenticationManagerBean()); - return jwtAuthenticationFilter; - } + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper, + AuthenticationManager manager, + JwtTokenService jwtTokenService) { + JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter( + new JwtMatcher("/**", PERMIT_ALL), entryPoint(), securityHelper, jwtTokenService); + jwtAuthenticationFilter.setAuthenticationManager(manager); + return jwtAuthenticationFilter; + } } diff --git a/backend/src/main/java/ru/micord/ervu/security/SecurityConstants.java b/backend/src/main/java/ru/micord/ervu/security/SecurityConstants.java new file mode 100644 index 00000000..1853716d --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/SecurityConstants.java @@ -0,0 +1,5 @@ +package ru.micord.ervu.security; + +public class SecurityConstants { + public static final String ESIA_LOGOUT = "/esia/logout"; +} 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 abbc0932..0a94fe36 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,13 +3,14 @@ package ru.micord.ervu.security.esia.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import ru.micord.ervu.security.SecurityConstants; import ru.micord.ervu.security.esia.model.OrgInfoModel; import ru.micord.ervu.security.esia.service.EsiaAuthService; import ru.micord.ervu.security.esia.service.EsiaDataService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -25,38 +26,33 @@ public class EsiaController { @Autowired private EsiaDataService esiaDataService; - @RequestMapping(value = "/esia/url") + @GetMapping(value = "/esia/url") public String getEsiaUrl() { return esiaAuthService.generateAuthCodeUrl(); } - @RequestMapping(value = "/esia/auth", params = "code", method = RequestMethod.GET) + @GetMapping(value = "/esia/auth", params = "code") public ResponseEntity> esiaAuth(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) { return esiaAuthService.getEsiaTokensByCode(code, request, response); } - @RequestMapping(value = "/esia/refresh") + @PostMapping(value = "/esia/refresh") public void refreshToken(HttpServletRequest request, HttpServletResponse response) { esiaAuthService.getEsiaTokensByRefreshToken(request, response); } - @RequestMapping(value = "/esia/org") + @GetMapping(value = "/esia/org") public OrgInfoModel getOrgInfo(HttpServletRequest request) { return esiaDataService.getOrgInfo(request); } - @RequestMapping(value = "/esia/userfullname") + @GetMapping(value = "/esia/userfullname") public String getUserFullname(HttpServletRequest request) { return esiaDataService.getUserFullname(request); } - @RequestMapping(value = "/esia/orgunitname") + @GetMapping(value = "/esia/orgunitname") public String getOrgUnitName(HttpServletRequest request) { return esiaDataService.getOrgUnitName(request); } - - @RequestMapping(value = "/esia/logout") - public String logout(HttpServletRequest request, HttpServletResponse response) { - return esiaAuthService.logout(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 c6cf3818..55b24209 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 @@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.ObjectMapper; import ervu.service.okopf.OkopfService; +import ru.micord.ervu.security.esia.token.TokensStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -46,37 +47,31 @@ import ru.micord.ervu.security.esia.model.EsiaTokenResponse; import ru.micord.ervu.security.esia.model.FormUrlencoded; import ru.micord.ervu.security.esia.model.OrganizationModel; 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 */ @Service public class EsiaAuthService { - private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - @Value("${cookie-path:#{null}}") - private String path; - @Autowired private ObjectMapper objectMapper; - @Autowired private EsiaConfig esiaConfig; - @Autowired private UlDataService ulDataService; - @Autowired private JwtTokenService jwtTokenService; - @Autowired private ReplyingKafkaService replyingKafkaService; - @Autowired private OkopfService okopfService; + @Autowired + private SecurityHelper securityHelper; @Value("${ervu.kafka.org.reply.topic}") private String requestReplyTopic; @@ -205,44 +200,31 @@ public class EsiaAuthService { .build() .send(postReq, HttpResponse.BodyHandlers.ofString()); String responseString = postResp.body(); - EsiaTokenResponse tokenResponse = objectMapper.readValue(responseString, EsiaTokenResponse.class); - if (tokenResponse != null && tokenResponse.getError() != null) { + EsiaTokenResponse tokenResponse = objectMapper.readValue(responseString, + EsiaTokenResponse.class + ); + + if (tokenResponse == null) { + throw new IllegalStateException("Got empty esia response"); + } + + if (tokenResponse.getError() != null) { throw new RuntimeException(tokenResponse.getError_description()); } String accessToken = tokenResponse.getAccess_token(); + boolean hasRole = ulDataService.checkRole(accessToken); EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken); String prnOid = esiaAccessToken.getSbj_id(); - if (!hasRole) { - LOGGER.error("The user with id = " + prnOid + " does not have the required role"); - return new ResponseEntity<>( - "Доступ запрещен. Пользователь должен быть включен в группу \"Сотрудник, ответственный за военно-учетную работу\" в ЕСИА", - HttpStatus.FORBIDDEN - ); - } - String cookiePath = null; - if (path != null) { - cookiePath = path; - } - else { - cookiePath = request.getContextPath(); - } - Cookie cookie = new Cookie("access_token", accessToken); - cookie.setHttpOnly(true); - cookie.setPath(cookiePath); - response.addCookie(cookie); - String refreshToken = tokenResponse.getRefresh_token(); - Cookie cookieRefresh = new Cookie("refresh_token", refreshToken); - cookieRefresh.setHttpOnly(true); - cookieRefresh.setPath(cookiePath); - response.addCookie(cookieRefresh); - 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()); - authToken.setPath(cookiePath); - authToken.setHttpOnly(true); - response.addCookie(authToken); + String ervuId = getErvuId(accessToken, prnOid); + Long expiresIn = tokenResponse.getExpires_in(); + TokensStore.addAccessToken(prnOid, accessToken, expiresIn); + TokensStore.addRefreshToken(prnOid, refreshToken, expiresIn); + Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, hasRole); + int expiry = tokenResponse.getExpires_in().intValue(); + Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry); + response.addCookie(accessCookie); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); SecurityContext context = SecurityContextHolder.createEmptyContext(); @@ -250,11 +232,15 @@ public class EsiaAuthService { esiaAccessToken.getSbj_id(), token.getValue()); context.setAuthentication(authentication); SecurityContextHolder.setContext(context); - - Cookie isAuth = new Cookie("webbpm.ervu-lkrp-ul", "true"); - isAuth.setMaxAge(tokenResponse.getExpires_in().intValue()); - isAuth.setPath("/"); - response.addCookie(isAuth); + Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry); + response.addCookie(authMarkerCookie); + if (!hasRole) { + LOGGER.error("The user with id = " + prnOid + " does not have the required role"); + return new ResponseEntity<>( + "Доступ запрещен. Пользователь должен быть включен в группу \"Сотрудник, ответственный за военно-учетную работу\" в ЕСИА", + HttpStatus.FORBIDDEN + ); + } return ResponseEntity.ok("Authentication successful"); } catch (Exception e) { @@ -264,15 +250,7 @@ public class EsiaAuthService { public void getEsiaTokensByRefreshToken(HttpServletRequest request, HttpServletResponse response) { try { - String refreshToken = null; - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("refresh_token")) { - refreshToken = cookie.getValue(); - } - } - } + String refreshToken = jwtTokenService.getRefreshToken(request); String clientId = esiaConfig.getClientId(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss xx"); ZonedDateTime dt = ZonedDateTime.now(); @@ -321,30 +299,17 @@ public class EsiaAuthService { throw new RuntimeException(tokenResponse.getError_description()); } String accessToken = tokenResponse.getAccess_token(); - Cookie cookie = new Cookie("access_token", accessToken); - cookie.setHttpOnly(true); - String cookiePath = null; - if (path != null) { - cookiePath = path; - } - else { - cookiePath = request.getContextPath(); - } - cookie.setPath(cookiePath); - response.addCookie(cookie); - String newRefreshToken = tokenResponse.getRefresh_token(); - Cookie cookieRefresh = new Cookie("refresh_token", newRefreshToken); - 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()); - authToken.setPath(cookiePath); - authToken.setHttpOnly(true); - response.addCookie(authToken); + String prnOid = esiaAccessToken.getSbj_id(); + Long expiresIn = tokenResponse.getExpires_in(); + TokensStore.addAccessToken(prnOid, accessToken, expiresIn); + TokensStore.addRefreshToken(prnOid, newRefreshToken, expiresIn); + String ervuId = getErvuId(accessToken, prnOid); + Token token = jwtTokenService.createAccessToken(esiaAccessToken.getSbj_id(), expiresIn, ervuId, true); + int expiry = tokenResponse.getExpires_in().intValue(); + Cookie accessCookie = securityHelper.createAccessCookie(token.getValue(), expiry); + response.addCookie(accessCookie); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); SecurityContext context = SecurityContextHolder.createEmptyContext(); @@ -352,11 +317,8 @@ public class EsiaAuthService { esiaAccessToken.getSbj_id(), token.getValue()); context.setAuthentication(authentication); SecurityContextHolder.setContext(context); - - Cookie isAuth = new Cookie("webbpm.ervu-lkrp-ul", "true"); - isAuth.setMaxAge(tokenResponse.getExpires_in().intValue()); - isAuth.setPath("/"); - response.addCookie(isAuth); + Cookie authMarkerCookie = securityHelper.createAuthMarkerCookie("true", expiry); + response.addCookie(authMarkerCookie); } catch (Exception e) { throw new RuntimeException(e); @@ -397,23 +359,10 @@ public class EsiaAuthService { public String logout(HttpServletRequest request, HttpServletResponse response) { try { - Cookie[] cookies = request.getCookies(); - if (cookies != null) - for (Cookie cookie : cookies) { - if (cookie.getName().equals("webbpm.ervu-lkrp-ul")) { - cookie.setValue(""); - cookie.setPath("/"); - cookie.setMaxAge(0); - response.addCookie(cookie); - } - else if (cookie.getName().equals("auth_token") || cookie.getName().equals("refresh_token") - || cookie.getName().equals("access_token")) { - cookie.setValue(""); - cookie.setPath(cookie.getPath()); - cookie.setMaxAge(0); - response.addCookie(cookie); - } - } + securityHelper.clearAccessCookies(response); + String userId = jwtTokenService.getUserAccountId(request); + TokensStore.removeAccessToken(userId); + TokensStore.removeRefreshToken(userId); String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl(); String redirectUrl = esiaConfig.getRedirectUrl(); URL url = new URL(logoutUrl); 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 537d99ac..944fdb4a 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 @@ -1,12 +1,18 @@ package ru.micord.ervu.security.esia.service; import java.util.Arrays; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import ru.micord.ervu.security.esia.model.*; +import ru.micord.ervu.security.esia.model.Addresses; +import ru.micord.ervu.security.esia.model.Contacts; +import ru.micord.ervu.security.esia.model.EmployeeModel; +import ru.micord.ervu.security.esia.model.EsiaAccessToken; +import ru.micord.ervu.security.esia.model.OrgInfoModel; +import ru.micord.ervu.security.esia.model.OrganizationModel; +import ru.micord.ervu.security.esia.model.PersonModel; +import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService; /** * @author Eduard Tihomirov @@ -17,8 +23,11 @@ public class EsiaDataService { @Autowired private UlDataService ulDataService; + @Autowired + private JwtTokenService jwtTokenService; + public OrgInfoModel getOrgInfo(HttpServletRequest request) { - String accessToken = getAccessToken(request); + String accessToken = jwtTokenService.getAccessToken(request); if (accessToken == null) { return null; } @@ -69,7 +78,7 @@ public class EsiaDataService { } public String getOrgUnitName(HttpServletRequest request) { - String accessToken = getAccessToken(request); + String accessToken = jwtTokenService.getAccessToken(request); if (accessToken == null) { return null; } @@ -78,7 +87,7 @@ public class EsiaDataService { } public String getUserFullname(HttpServletRequest request) { - String accessToken = getAccessToken(request); + String accessToken = jwtTokenService.getAccessToken(request); if (accessToken == null) { return null; } @@ -86,16 +95,4 @@ public class EsiaDataService { PersonModel personModel = ulDataService.getPersonData(esiaAccessToken.getSbj_id(), accessToken); return personModel.getLastName() + " " + personModel.getFirstName().charAt(0) + ". " + personModel.getMiddleName().charAt(0) + "."; } - - private String getAccessToken(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("access_token")) { - return cookie.getValue(); - } - } - } - return null; - } } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/token/ExpiringToken.java b/backend/src/main/java/ru/micord/ervu/security/esia/token/ExpiringToken.java new file mode 100644 index 00000000..f6a476e4 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/token/ExpiringToken.java @@ -0,0 +1,34 @@ +package ru.micord.ervu.security.esia.token; + +/** + * @author Eduard Tihomirov + */ +public class ExpiringToken { + private String accessToken; + private long expiryTime; + + public ExpiringToken(String accessToken, long expiryTime) { + this.accessToken = accessToken; + this.expiryTime = expiryTime; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public long getExpiryTime() { + return expiryTime; + } + + public void setExpiryTime(long expiryTime) { + this.expiryTime = expiryTime; + } + + boolean isExpired() { + return System.currentTimeMillis() > expiryTime; + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensClearShedulerService.java b/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensClearShedulerService.java new file mode 100644 index 00000000..46652958 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensClearShedulerService.java @@ -0,0 +1,20 @@ +package ru.micord.ervu.security.esia.token; + +import net.javacrumbs.shedlock.core.SchedulerLock; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author Eduard Tihomirov + */ +@Service +public class TokensClearShedulerService { + @Scheduled(cron = "${esia.token.clear.cron:0 0 */1 * * *}") + @SchedulerLock(name = "clearToken") + @Transactional + public void load() { + TokensStore.removeExpiredRefreshToken(); + TokensStore.removeExpiredAccessToken(); + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensStore.java b/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensStore.java new file mode 100644 index 00000000..2c93fc1c --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/esia/token/TokensStore.java @@ -0,0 +1,61 @@ +package ru.micord.ervu.security.esia.token; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Eduard Tihomirov + */ +public class TokensStore { + private static final Map accessTokensMap = new ConcurrentHashMap<>(); + private static final Map refreshTokensMap = new ConcurrentHashMap<>(); + + public static void addAccessToken(String prnOid, String token, long expiresIn) { + if (token != null) { + long expiryTime = System.currentTimeMillis() + 1000L * expiresIn; + accessTokensMap.put(prnOid, new ExpiringToken(token, expiryTime)); + } + } + + public static String getAccessToken(String prnOid) { + return accessTokensMap.get(prnOid).getAccessToken(); + } + + public static void removeExpiredAccessToken() { + for (String key : accessTokensMap.keySet()) { + ExpiringToken token = accessTokensMap.get(key); + if (token != null && token.isExpired()) { + accessTokensMap.remove(key); + } + } + } + + public static void removeExpiredRefreshToken() { + for (String key : refreshTokensMap.keySet()) { + ExpiringToken token = refreshTokensMap.get(key); + if (token != null && token.isExpired()) { + refreshTokensMap.remove(key); + } + } + } + + public static void removeAccessToken(String prnOid) { + accessTokensMap.remove(prnOid); + } + + public static void addRefreshToken(String prnOid, String token, long expiresIn) { + if (token != null) { + long expiryTime = System.currentTimeMillis() + 1000L * expiresIn; + refreshTokensMap.put(prnOid, new ExpiringToken(token, expiryTime)); + } + } + + public static String getRefreshToken(String prnOid) { + return refreshTokensMap.get(prnOid).getAccessToken(); + } + + public static void removeRefreshToken(String prnOid) { + refreshTokensMap.remove(prnOid); + } + +} diff --git a/backend/src/main/java/ru/micord/ervu/security/filter/FilterChainExceptionHandler.java b/backend/src/main/java/ru/micord/ervu/security/filter/FilterChainExceptionHandler.java new file mode 100644 index 00000000..7cce4a1b --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/filter/FilterChainExceptionHandler.java @@ -0,0 +1,29 @@ +package ru.micord.ervu.security.filter; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExceptionResolver; + +@Component +public class FilterChainExceptionHandler extends OncePerRequestFilter { + @Autowired + @Qualifier("handlerExceptionResolver") + private HandlerExceptionResolver resolver; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) { + try { + filterChain.doFilter(request, response); + } + catch (Exception e) { + resolver.resolveException(request, response, null, e); + } + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtAuthenticationProvider.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtAuthenticationProvider.java index 67e359c7..f709679f 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtAuthenticationProvider.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtAuthenticationProvider.java @@ -1,5 +1,7 @@ package ru.micord.ervu.security.webbpm.jwt; +import java.util.Collections; + import io.jsonwebtoken.ExpiredJwtException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; @@ -44,10 +46,12 @@ public class JwtAuthenticationProvider implements AuthenticationProvider { throw new BadCredentialsException("Auth token is not valid for user " + token.getUserAccountId()); } - UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = - new UsernamePasswordAuthenticationToken(token.getUserAccountId(), null); + UsernamePasswordAuthenticationToken pwdToken = + UsernamePasswordAuthenticationToken.authenticated(token.getUserAccountId(), null, + Collections.emptyList() + ); - return new JwtAuthentication(usernamePasswordAuthenticationToken, token.getUserAccountId(), token.getValue()); + return new JwtAuthentication(pwdToken, token.getUserAccountId(), token.getValue()); } @Override diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtMatcher.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtMatcher.java new file mode 100644 index 00000000..f0c90cec --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/JwtMatcher.java @@ -0,0 +1,33 @@ +package ru.micord.ervu.security.webbpm.jwt; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken; + +public final class JwtMatcher implements RequestMatcher { + private final Set excludedPathMatchers; + private final AntPathRequestMatcher securedMatcher; + + public JwtMatcher(String securedPath, String... excludedPaths) { + this.securedMatcher = new AntPathRequestMatcher(securedPath); + this.excludedPathMatchers = Arrays.stream(excludedPaths) + .map(AntPathRequestMatcher::new) + .collect(Collectors.toSet()); + } + + @Override + public boolean matches(HttpServletRequest request) { + if (this.excludedPathMatchers.stream().anyMatch(matcher -> matcher.matches(request))) { + return false; + } + else { + return extractAuthToken(request) != null && this.securedMatcher.matches(request); + } + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java index a27c5b9d..a030df8c 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/filter/JwtAuthenticationFilter.java @@ -4,10 +4,10 @@ import java.io.IOException; import java.lang.invoke.MethodHandles; import javax.servlet.FilterChain; import javax.servlet.ServletException; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import io.jsonwebtoken.ExpiredJwtException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.CredentialsExpiredException; @@ -16,70 +16,80 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.RequestMatcher; import ru.micord.ervu.security.webbpm.jwt.JwtAuthentication; +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; + /** * @author Flyur Karimov */ public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter { - private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static final Logger LOGGER = LoggerFactory.getLogger( + MethodHandles.lookup().lookupClass()); private final AuthenticationEntryPoint entryPoint; - public JwtAuthenticationFilter(String securityPath, AuthenticationEntryPoint entryPoint) { - super(securityPath); + private final SecurityHelper securityHelper; + + private final JwtTokenService jwtTokenService; + + public JwtAuthenticationFilter(RequestMatcher requestMatcher, + AuthenticationEntryPoint entryPoint, + SecurityHelper securityHelper, + JwtTokenService jwtTokenService) { + super(requestMatcher); this.entryPoint = entryPoint; + this.securityHelper = securityHelper; + this.jwtTokenService = jwtTokenService; } @Override public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, - HttpServletResponse httpServletResponse) throws AuthenticationException { - String token = extractAuthTokenFromRequest(httpServletRequest); + HttpServletResponse httpServletResponse) + throws AuthenticationException { + String tokenStr = extractAuthToken(httpServletRequest); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { - authentication = new JwtAuthentication(null, null, token); + authentication = new JwtAuthentication(null, null, tokenStr); } try { authentication = getAuthenticationManager().authenticate(authentication); + 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) { + securityHelper.clearAccessCookies(httpServletResponse); httpServletResponse.setStatus(401); LOGGER.warn(e.getMessage()); + return null; } return authentication; } - @Override - protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) { - return extractAuthTokenFromRequest(request) != null; - } - @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, - FilterChain chain, Authentication authentication) throws IOException, ServletException { + FilterChain chain, Authentication authentication) + throws IOException, ServletException { SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } @Override - protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException, ServletException { + protected void unsuccessfulAuthentication(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) + throws IOException, ServletException { LOGGER.error("Jwt unsuccessful authentication exception", exception); SecurityContextHolder.clearContext(); entryPoint.commence(request, response, exception); } - - public String extractAuthTokenFromRequest(HttpServletRequest httpRequest) { - String token = null; - Cookie[] cookies = httpRequest.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("auth_token")) { - token = cookie.getValue(); - LOGGER.info("Token extracted from cookie: {}", token); - } - } - } - return token; - } } diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java new file mode 100644 index 00000000..0a222c79 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/helper/SecurityHelper.java @@ -0,0 +1,43 @@ +package ru.micord.ervu.security.webbpm.jwt.helper; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Value; +import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil; + +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.createCookie; + +public final class SecurityHelper { + @Value("${cookie.path:#{null}}") + private String accessCookiePath; + + public void clearAccessCookies(HttpServletResponse response) { + Cookie tokenCookie = createCookie(AUTH_TOKEN, null, null); + tokenCookie.setMaxAge(0); + tokenCookie.setPath(accessCookiePath); + tokenCookie.setHttpOnly(true); + response.addCookie(tokenCookie); + + Cookie markerCookie = createCookie(AUTH_MARKER, null, null); + markerCookie.setMaxAge(0); + markerCookie.setPath("/"); + response.addCookie(markerCookie); + } + + public Cookie createAccessCookie(String cookieValue, int expiry) { + Cookie authToken = createCookie(SecurityUtil.AUTH_TOKEN, cookieValue, accessCookiePath); + authToken.setPath(accessCookiePath); + authToken.setMaxAge(expiry); + return authToken; + } + + public Cookie createAuthMarkerCookie(String cookieValue, int expiry) { + Cookie marker = createCookie(AUTH_MARKER, cookieValue, "/"); + marker.setMaxAge(expiry); + marker.setHttpOnly(false); + return marker; + } +} diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/model/Token.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/model/Token.java index d7da8527..c64413ed 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/model/Token.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/model/Token.java @@ -7,12 +7,14 @@ public class Token { private final String issuer; private final Date expirationDate; private final String value; + private final Boolean hasRole; - public Token(String userAccountId, String issuer, Date expirationDate, String value) { + public Token(String userAccountId, String issuer, Date expirationDate, String value, Boolean hasRole) { this.userAccountId = userAccountId; this.issuer = issuer; this.expirationDate = expirationDate; this.value = value; + this.hasRole = hasRole; } public String getUserAccountId() { @@ -34,4 +36,8 @@ public class Token { public String getValue() { return value; } + + public Boolean getHasRole() { + return hasRole; + } } diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java index 645b582d..b04c226c 100644 --- a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/service/JwtTokenService.java @@ -4,6 +4,7 @@ import java.lang.invoke.MethodHandles; import java.util.Base64; import java.util.Date; import javax.crypto.SecretKey; +import javax.servlet.http.HttpServletRequest; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -13,10 +14,13 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import ru.micord.ervu.security.esia.token.TokensStore; import ru.micord.ervu.security.webbpm.jwt.model.Token; import ru.cg.webbpm.modules.resources.api.ResourceMetadataUtils; +import static ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil.extractAuthToken; + /** * @author Flyur Karimov */ @@ -38,7 +42,7 @@ public class JwtTokenService { this.SIGNING_KEY = Keys.hmacShaKeyFor(encodedKey); } - public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId) { + public Token createAccessToken(String userAccountId, Long expiresIn, String ervuId, Boolean hasRole) { Date expirationDate = new Date(System.currentTimeMillis() + 1000L * expiresIn); String value = Jwts.builder() @@ -46,9 +50,10 @@ public class JwtTokenService { .setIssuer(tokenIssuerName) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(expirationDate) + .claim("hasRole", hasRole) .signWith(SIGNING_KEY) .compact(); - return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value); + return new Token(userAccountId + ":" + ervuId, tokenIssuerName, expirationDate, value, hasRole); } public boolean isValid(Token token) { @@ -70,6 +75,26 @@ public class JwtTokenService { .parseClaimsJws(token) .getBody(); - return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token); + return new Token(claims.getSubject(), claims.getIssuer(), claims.getExpiration(), token, claims.get("hasRole", Boolean.class)); + } + + public String getAccessToken(HttpServletRequest request) { + return TokensStore.getAccessToken(getUserAccountId(request)); + } + + public String getRefreshToken(HttpServletRequest request) { + return TokensStore.getRefreshToken(getUserAccountId(request)); + } + + public String getUserAccountId(HttpServletRequest request) { + String authToken = extractAuthToken(request); + + if (authToken != null) { + String[] ids = getToken(authToken).getUserAccountId().split(":"); + return ids[0]; + } + else { + throw new RuntimeException("Failed to get auth data. User unauthorized."); + } } } diff --git a/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/util/SecurityUtil.java b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/util/SecurityUtil.java new file mode 100644 index 00000000..b2314e37 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/security/webbpm/jwt/util/SecurityUtil.java @@ -0,0 +1,45 @@ +package ru.micord.ervu.security.webbpm.jwt.util; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.util.WebUtils; + +import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST; + +public final class SecurityUtil { + public static final String AUTH_TOKEN = "auth_token"; + + public static final String AUTH_MARKER = "webbpm.ervu-lkrp-ul"; + + private SecurityUtil() { + //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) { + Cookie cookie = WebUtils.getCookie(httpRequest, AUTH_TOKEN); + return cookie != null ? cookie.getValue() : null; + } +} diff --git a/config/micord.env b/config/micord.env index 87cfcff6..205c80ab 100644 --- a/config/micord.env +++ b/config/micord.env @@ -51,3 +51,6 @@ ERVU_FILE_UPLOAD_FILE_SIZE_THRESHOLD=0 S3_ENDPOINT=http://ervu-minio.k8s.micord.ru:31900 S3_ACCESS_KEY=rlTdTvkmSXu9FsLhfecw S3_SECRET_KEY=NUmY0wwRIEyAd98GCKd1cOgJWvLQYAcMMul5Ulu0 + +ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * * +COOKIE_PATH=/ul \ No newline at end of file diff --git a/config/standalone/dev/standalone.xml b/config/standalone/dev/standalone.xml index 59fdae3a..0dd43d2e 100644 --- a/config/standalone/dev/standalone.xml +++ b/config/standalone/dev/standalone.xml @@ -94,6 +94,7 @@ + diff --git a/distribution/pom.xml b/distribution/pom.xml index 08e3ffbd..dbdea4b7 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -4,7 +4,7 @@ ru.micord.ervu.lkrp ul - 1.9.0-SNAPSHOT + 1.8.2 ru.micord.ervu.lkrp.ul diff --git a/frontend/pom.xml b/frontend/pom.xml index 01008484..5a46ea33 100644 --- a/frontend/pom.xml +++ b/frontend/pom.xml @@ -4,7 +4,7 @@ ru.micord.ervu.lkrp ul - 1.9.0-SNAPSHOT + 1.8.2 ru.micord.ervu.lkrp.ul diff --git a/frontend/src/resources/template/app/component/log_out.html b/frontend/src/resources/template/app/component/log_out.html index cf7831d2..996c245f 100644 --- a/frontend/src/resources/template/app/component/log_out.html +++ b/frontend/src/resources/template/app/component/log_out.html @@ -1,6 +1,7 @@ -{{getUserFullname()}} - +{{getUserFullname()}} + {{getOrgUnitName()}} Данные организации Выйти - \ No newline at end of file + +Выйти diff --git a/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts b/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts index 3e0da6a8..4ed140be 100644 --- a/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts +++ b/frontend/src/ts/ervu/component/fileupload/ErvuFileUpload.ts @@ -11,6 +11,8 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from import {FileItem, FileUploader} from "ng2-file-upload"; import {FileLikeObject} from "ng2-file-upload/file-upload/file-like-object.class"; import {EmployeeInfoFileFormType} from "./EmployeeInfoFileFormType"; +import {CookieService} from "ngx-cookie"; +import {TokenConstants} from "../../../modules/security/TokenConstants"; @Component({ moduleId: module.id, @@ -59,6 +61,7 @@ export class ErvuFileUpload extends InputControl { private messagesService: MessagesService; private isUploadErrorOccurred = false; private appConfigService: AppConfigService; + private cookieService: CookieService; constructor(el: ElementRef, cd: ChangeDetectorRef) { super(el, cd); @@ -69,6 +72,7 @@ export class ErvuFileUpload extends InputControl { super.initialize(); this.messagesService = this.injector.get(MessagesService); this.appConfigService = this.injector.get(AppConfigService); + this.cookieService = this.injector.get(CookieService); this.url = `/${this.appConfigService.getParamValue(ErvuFileUpload.BACKEND_URL)}/employee/document`; this.uploader.setOptions({ @@ -99,6 +103,10 @@ export class ErvuFileUpload extends InputControl { { name: "Client-Time-Zone", value: Intl.DateTimeFormat().resolvedOptions().timeZone + }, + { + name: TokenConstants.CSRF_HEADER_NAME, + value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME) } ] }); @@ -129,6 +137,24 @@ export class ErvuFileUpload extends InputControl { private setUploaderMethods() { this.uploader.onBeforeUploadItem = (fileItem: FileItem) => { + + //refresh headers + this.uploader.setOptions({ + headers: [ + { + name: "X-Employee-Info-File-Form-Type", + value: EmployeeInfoFileFormType[this.formType] + }, + { + name: "Client-Time-Zone", + value: Intl.DateTimeFormat().resolvedOptions().timeZone + }, + { + name: TokenConstants.CSRF_HEADER_NAME, + value: this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME) + } + ] + }); this.fileUploadStartEvent.trigger(); this.isDropZoneVisible = false; this.isFilesListVisible = false; @@ -239,4 +265,4 @@ export class ErvuFileUpload extends InputControl { this.isUploadErrorOccurred = false; this.cd.markForCheck(); } -} \ No newline at end of file +} diff --git a/frontend/src/ts/esia/OrgDataRoot.ts b/frontend/src/ts/esia/OrgDataRoot.ts index feee9eb9..901362ed 100644 --- a/frontend/src/ts/esia/OrgDataRoot.ts +++ b/frontend/src/ts/esia/OrgDataRoot.ts @@ -2,10 +2,10 @@ import {AnalyticalScope, Behavior, Container, ControlWithValue} from "@webbpm/ba import {HttpClient} from "@angular/common/http"; import {OrgData} from "./OrgData"; import {OrgInfoModel} from "../generated/ru/micord/ervu/security/esia/model/OrgInfoModel"; -import {CookieService} from "ngx-cookie"; +import {AuthenticationService} from "../modules/security/authentication.service"; @AnalyticalScope(Container) -export class OrgDataRoot extends Behavior{ +export class OrgDataRoot extends Behavior { private container: Container; @@ -14,8 +14,9 @@ export class OrgDataRoot extends Behavior{ this.container = this.getScript(Container); let orgScripts: OrgData[] = this.container.getScriptsInThisAndChildren(OrgData); let httpClient = this.injector.get(HttpClient); - let cookieService = this.injector.get(CookieService); - if (cookieService.get("webbpm.ervu-lkrp-ul")) { + let cookieService = this.injector.get(AuthenticationService); + + if (cookieService.isAuthenticated()) { httpClient.get("esia/org") .toPromise() .then(orgInfoModel => { @@ -23,9 +24,9 @@ export class OrgDataRoot extends Behavior{ return; } for (let orgData of orgScripts) { - let control: ControlWithValue = orgData.getScriptInObject(orgData.getObjectId(), - 'component.ControlWithValue'); - control.setValue(orgInfoModel[orgData.dataId]); + let control: ControlWithValue = orgData.getScriptInObject(orgData.getObjectId(), + 'component.ControlWithValue'); + control.setValue(orgInfoModel[orgData.dataId]); } }); } diff --git a/frontend/src/ts/modules/app/app.module.ts b/frontend/src/ts/modules/app/app.module.ts index 092f4970..4f1ac1b3 100644 --- a/frontend/src/ts/modules/app/app.module.ts +++ b/frontend/src/ts/modules/app/app.module.ts @@ -1,4 +1,4 @@ -import {forwardRef, NgModule} from "@angular/core"; +import {APP_INITIALIZER, forwardRef, NgModule} from "@angular/core"; import {NgbModule} from "@ng-bootstrap/ng-bootstrap"; import {CommonModule, registerLocaleData} from "@angular/common"; import localeRu from '@angular/common/locales/ru'; @@ -25,6 +25,7 @@ import {FileUploadModule} from "ng2-file-upload"; import {ErvuFileUpload} from "../../ervu/component/fileupload/ErvuFileUpload"; import {InMemoryStaticGrid} from "../../ervu/component/grid/InMemoryStaticGrid"; import {ErvuDownloadFileButton} from "../../ervu/component/button/ErvuDownloadFileButton"; +import {AuthenticationService} from "../security/authentication.service"; registerLocaleData(localeRu); export const DIRECTIVES = [ @@ -39,6 +40,10 @@ export const DIRECTIVES = [ forwardRef(() => InMemoryStaticGrid) ]; +export function checkAuthentication(authService: AuthenticationService): () => Promise { + return () => authService.checkAuthentication(); +} + @NgModule({ imports: [ CommonModule, @@ -60,6 +65,13 @@ export const DIRECTIVES = [ DIRECTIVES ], providers: [ + AuthenticationService, + { + provide: APP_INITIALIZER, + useFactory: checkAuthentication, + deps: [AuthenticationService], + multi: true, + }, { provide: ProgressIndicationService, useClass: AppProgressIndicationService } ], bootstrap: [], diff --git a/frontend/src/ts/modules/app/component/logout.component.ts b/frontend/src/ts/modules/app/component/logout.component.ts index e8c3b8be..0ac178ae 100644 --- a/frontend/src/ts/modules/app/component/logout.component.ts +++ b/frontend/src/ts/modules/app/component/logout.component.ts @@ -1,7 +1,7 @@ import {ChangeDetectorRef, Component, OnInit} from "@angular/core"; import {Router} from "@angular/router"; import {HttpClient} from "@angular/common/http"; -import {CookieService} from "ngx-cookie"; +import {AuthenticationService} from "../../security/authentication.service"; @Component({ moduleId: module.id, @@ -13,13 +13,12 @@ export class LogOutComponent implements OnInit{ private userFullname: string; private orgUnitName: string; - constructor(private router: Router, private httpClient: HttpClient, - private cookieService: CookieService, private cd: ChangeDetectorRef) { + private authenticationService: AuthenticationService, private cd: ChangeDetectorRef) { } ngOnInit(): void { - let isAuth = this.getIsAuth(); + let isAuth = this.authenticationService.isAuthenticated(); if (isAuth) { Promise.all([ this.httpClient.get("esia/userfullname").toPromise(), @@ -33,20 +32,18 @@ export class LogOutComponent implements OnInit{ } public logout(): void { - this.httpClient.get("esia/logout").toPromise().then(url => { - window.open(url, "_self"); - }) + this.authenticationService.logout(); } public getUserFullname(): string { return this.userFullname; } - public getIsAuth(): boolean { - return this.cookieService.get("webbpm.ervu-lkrp-ul") != null; + public isAuthenticated(): boolean { + return this.authenticationService.isAuthenticated(); } public getOrgUnitName(): string { return this.orgUnitName; } -} \ No newline at end of file +} diff --git a/frontend/src/ts/modules/security/TokenConstants.ts b/frontend/src/ts/modules/security/TokenConstants.ts new file mode 100644 index 00000000..597fe759 --- /dev/null +++ b/frontend/src/ts/modules/security/TokenConstants.ts @@ -0,0 +1,4 @@ +export class TokenConstants { + public static readonly CSRF_TOKEN_NAME = "XSRF-TOKEN"; + public static readonly CSRF_HEADER_NAME = "X-XSRF-TOKEN"; +} diff --git a/frontend/src/ts/modules/security/authentication.service.ts b/frontend/src/ts/modules/security/authentication.service.ts new file mode 100644 index 00000000..03914174 --- /dev/null +++ b/frontend/src/ts/modules/security/authentication.service.ts @@ -0,0 +1,29 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Observable} from "rxjs"; +import {CookieService} from "ngx-cookie"; +import {tap} from "rxjs/operators"; +import {AppConfigService} from "@webbpm/base-package"; + +@Injectable({providedIn: 'root'}) +export class AuthenticationService { + + constructor(private http: HttpClient, + private cookieService: CookieService, + private appConfigService: AppConfigService) { + } + + checkAuthentication(): Promise{ + return this.appConfigService.load().then(value => this.http.get("version").toPromise()) + } + + logout(): Promise { + return this.http.post('esia/logout', {}, { responseType: 'text' as 'json' }).toPromise().then(url => { + window.open(url, "_self"); + }); + } + + public isAuthenticated(): boolean { + return this.cookieService.get('webbpm.ervu-lkrp-ul') != null; + } +} diff --git a/frontend/src/ts/modules/security/guard/auth.guard.ts b/frontend/src/ts/modules/security/guard/auth.guard.ts index 87201687..c65b84ab 100644 --- a/frontend/src/ts/modules/security/guard/auth.guard.ts +++ b/frontend/src/ts/modules/security/guard/auth.guard.ts @@ -2,8 +2,8 @@ import {Injectable} from "@angular/core"; import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router"; import {Observable} from "rxjs"; import {HttpClient, HttpParams} from "@angular/common/http"; -import {CookieService} from "ngx-cookie"; import {MessagesService} from "@webbpm/base-package"; +import {AuthenticationService} from "../authentication.service"; @Injectable({providedIn:'root'}) export abstract class AuthGuard implements CanActivate { @@ -11,7 +11,7 @@ export abstract class AuthGuard implements CanActivate { protected constructor( protected router: Router, private httpClient: HttpClient, - private cookieService: CookieService, + private authenticationService: AuthenticationService, private messageService: MessagesService ) { } @@ -42,22 +42,23 @@ export abstract class AuthGuard implements CanActivate { else if (code) { const params = new HttpParams().set('code', code); this.httpClient.get("esia/auth", - {params: params, responseType: 'text', observe: 'response'}) + { + params: params, responseType: 'text', observe: 'response', headers: { + "Error-intercept-skip": "true" + } + }) .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) - ); + (response) => { + window.open(url.origin + url.pathname, "_self"); + }) + .catch((reason) => { + let errorMessage = reason.error.messages != null + ? reason.error.messages + : reason.error.replaceAll('\\', ''); + this.messageService.error(errorMessage); + console.error(reason); + }); return false; } else { @@ -72,12 +73,8 @@ export abstract class AuthGuard implements CanActivate { }); } - private checkAccess(): Promise | boolean { - return this.getIsAuth() != null; - }; - - public getIsAuth(): string { - return this.cookieService.get('webbpm.ervu-lkrp-ul'); + private checkAccess(): boolean { + return this.authenticationService.isAuthenticated(); } private extractCode(message: string): string | null { diff --git a/frontend/src/ts/modules/webbpm/interceptor/absolute-url-csrf.interceptor.ts b/frontend/src/ts/modules/webbpm/interceptor/absolute-url-csrf.interceptor.ts new file mode 100644 index 00000000..ea231886 --- /dev/null +++ b/frontend/src/ts/modules/webbpm/interceptor/absolute-url-csrf.interceptor.ts @@ -0,0 +1,26 @@ +import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {CookieService} from "ngx-cookie"; +import {TokenConstants} from "../../security/TokenConstants"; + +@Injectable() +export class AbsoluteUrlCsrfInterceptor implements HttpInterceptor { + + constructor(private cookieService: CookieService) { + } + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + + let requestToForward = req; + let token = this.cookieService.get(TokenConstants.CSRF_TOKEN_NAME) as string; + + if (token != null) { + let headers = {}; + let headerName = TokenConstants.CSRF_HEADER_NAME; + headers[headerName] = token; + requestToForward = req.clone({setHeaders: headers}); + } + return next.handle(requestToForward); + } +} diff --git a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts index 07735d52..6cd9ffe7 100644 --- a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts +++ b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts @@ -4,9 +4,11 @@ import { HttpSecurityErrorInterceptor, HttpSecurityInterceptor } from "@webbpm/base-package"; +import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor"; export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true}, - {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true} ]; diff --git a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts index ee46e0c2..77b3b401 100644 --- a/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts +++ b/frontend/src/ts/modules/webbpm/interceptor/default-interceptors.ts @@ -1,9 +1,11 @@ import {HTTP_INTERCEPTORS} from "@angular/common/http"; import {FormDirtyInterceptor, HttpSecurityInterceptor} from "@webbpm/base-package"; import {DevHttpSecurityErrorInterceptor} from "./http-security-error-interceptor.dev"; +import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor"; export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: DevHttpSecurityErrorInterceptor, multi: true}, - {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true}, ]; diff --git a/frontend/src/ts/modules/webbpm/webbpm.module.ts b/frontend/src/ts/modules/webbpm/webbpm.module.ts index c9ef120c..3326d6e7 100644 --- a/frontend/src/ts/modules/webbpm/webbpm.module.ts +++ b/frontend/src/ts/modules/webbpm/webbpm.module.ts @@ -17,6 +17,8 @@ import { import {AppRoutingModule} from "../app/app-routing.module"; import {GlobalErrorHandler} from "./handler/global-error.handler.prod"; import {DEFAULT_HTTP_INTERCEPTOR_PROVIDERS} from "./interceptor/default-interceptors.prod"; +import {HttpClientModule, HttpClientXsrfModule} from "@angular/common/http"; +import {TokenConstants} from "../security/TokenConstants"; let IMPORTS = [ BrowserAnimationsModule, @@ -30,7 +32,10 @@ let IMPORTS = [ CoreModule, ComponentsModule, AppModule, - WebbpmRoutingModule + WebbpmRoutingModule, + HttpClientModule, + HttpClientXsrfModule.withOptions( + {cookieName: TokenConstants.CSRF_TOKEN_NAME, headerName: TokenConstants.CSRF_HEADER_NAME}) ]; @NgModule({ diff --git a/pom.xml b/pom.xml index 5e9479a3..7d18d257 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ru.micord.ervu.lkrp ul - 1.9.0-SNAPSHOT + 1.8.2 pom backend @@ -20,11 +20,33 @@ UTF-8 false 2.9.2 - 3.182.0 + 3.178.2 72000 + 6.15.0 + + org.springframework.security + spring-security-bom + 5.8.14 + import + pom + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + ru.cg.webbpm platform-bom @@ -41,36 +63,8 @@ ru.cg.webbpm.packages.base backend - 3.182.0 + 3.178.2 - - xerces - xercesImpl - - - ru.micord.fias - client - - - ru.micord.gar - gar-client - - - org.apache.tika - tika-core - - - org.mnode.ical4j - ical4j - - - org.apache.commons - commons-csv - - - org.springframework.ldap - spring-ldap-core - ru.cg.webbpm.modules.bpmn bpmn-workflow-api @@ -128,32 +122,20 @@ security-adapter - ru.cg.webbpm.modules.reporting.reporting-jasper - reporting-jasper-impl + ru.micord.fias + client - ru.cg.webbpm.modules.reporting.reporting-jasper - reporting-jasper-fonts + ru.micord.gar + gar-client - ru.cg.webbpm.modules.reporting.reporting-jasper - reporting-jasper-runtime-impl + net.javacrumbs.shedlock + shedlock-spring - ru.cg.webbpm.modules.reporting - reporting-api - - - ru.cg.webbpm.modules.reporting - reporting-runtime-api - - - ru.cg.webbpm.modules.reporting - reporting-runtime-impl - - - ru.cg.webbpm.modules.integration - telegram + net.javacrumbs.shedlock + shedlock-provider-jdbc-template @@ -174,6 +156,11 @@ resources ${project.version} + + ru.cg.webbpm.modules.reporting.reporting-jasper + reporting-jasper-fonts + ${webbpm-platform.version} + ru.cg.webbpm.modules.core core-runtime-api @@ -184,6 +171,11 @@ error-handling-api ${webbpm-platform.version} + + ru.cg.webbpm.modules.core + metrics + ${webbpm-platform.version} + ru.cg.webbpm.modules.resources resources-api @@ -221,6 +213,11 @@ ${webbpm-platform.version} runtime + + ru.cg.webbpm.modules.security + security-api + ${webbpm-platform.version} + ru.cg.webbpm.modules webkit-rpc @@ -251,11 +248,120 @@ inject ${webbpm-platform.version} + + ru.cg.webbpm.modules.reporting + reporting-api + ${webbpm-platform.version} + + + ru.cg.webbpm.modules.reporting + reporting-runtime-api + ${webbpm-platform.version} + + + ru.cg.webbpm.modules.reporting + reporting-runtime-impl + ${webbpm-platform.version} + runtime + + + ru.cg.webbpm.modules.reporting.reporting-jasper + reporting-jasper-impl + ${webbpm-platform.version} + runtime + + + ru.cg.webbpm.modules.reporting.reporting-jasper + reporting-jasper-runtime-impl + ${webbpm-platform.version} + runtime + + + bouncycastle + bcmail-jdk14 + + + bouncycastle + bcprov-jdk14 + + + org.bouncycastle + bcprov-jdk14 + + + org.bouncycastle + bcmail-jdk14 + + + + + ru.cg.webbpm.modules.reporting.reporting-xdoc + reporting-xdoc-impl + ${webbpm-platform.version} + runtime + + + ru.cg.webbpm.modules.reporting.reporting-xdoc + reporting-xdoc-runtime-impl + ${webbpm-platform.version} + runtime + + + ru.cg.webbpm + web-tests-core + ${webbpm-platform.version} + + + org.bouncycastle + bcprov-jdk15on + ${org.bouncycastle.version} + + + org.bouncycastle + bcpkix-jdk15on + ${org.bouncycastle.version} + org.springframework.kafka spring-kafka ${spring-kafka.version} + + org.apache.httpcomponents + httpcore + 4.4.12 + + + org.apache.httpcomponents + httpclient + 4.5.1 + + + org.mnode.ical4j + ical4j + 3.0.5 + + + org.apache.pdfbox + pdfbox + 2.0.21 + + + org.odftoolkit + odfdom-java + 0.8.7 + + + org.mockito + mockito-junit-jupiter + ${mockito-jupiter.version} + test + + + org.slf4j + slf4j-simple + 1.7.28 + org.slf4j slf4j-api @@ -358,7 +464,7 @@ maven-war-plugin - 3.1.0 + 3.1.0.cg1 true false diff --git a/resources/pom.xml b/resources/pom.xml index 8db8fd68..b158868c 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,7 +4,7 @@ ru.micord.ervu.lkrp ul - 1.9.0-SNAPSHOT + 1.8.2 ru.micord.ervu.lkrp.ul @@ -47,6 +47,43 @@ + + com.alexnederlof + jasperreports-plugin + 2.8 + + + net.sf.jasperreports + jasperreports-functions + ${jasperreports.version} + + + joda-time + joda-time + ${joda-time.version} + + + + + process-sources + + jasper + + + + + + net.sf.jasperreports.engine.design.JRJdtCompiler + src/main/resources + src/main/resources + .jasper + true + false + 4 + true + org.codehaus.plexus.compiler.util.scan.StaleSourceScanner + + org.apache.maven.plugins maven-clean-plugin