From bd187b2725aa4eab41c8510495497ae7a0778317 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 28 May 2025 15:53:25 +0530 Subject: [PATCH 01/13] role added --- .../ecd/controller/callallocation/CallAllocationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/ecd/controller/callallocation/CallAllocationController.java b/src/main/java/com/iemr/ecd/controller/callallocation/CallAllocationController.java index b9699b8..0ff7b98 100644 --- a/src/main/java/com/iemr/ecd/controller/callallocation/CallAllocationController.java +++ b/src/main/java/com/iemr/ecd/controller/callallocation/CallAllocationController.java @@ -142,7 +142,7 @@ public ResponseEntity insertRecordsInOutboundCalls(@RequestBody Outbound } - @GetMapping(value = "/getEligibleRecordsLanguageInfo/{psmId}/{phoneNoType}/{recordType}/{fDate}/{tDate}/{preferredLanguage}", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = "/getEligibleRecordsLanguageInfo/{psmId}/{phoneNoType}/{recordType}/{fDate}/{tDate}/{preferredLanguage}/{role}", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Fetch eligible Language records for allocation", description = "Desc - Fetch eligible records for allocation") @ApiResponses(value = { @ApiResponse(responseCode = CustomExceptionResponse.SUCCESS_SC_V, description = CustomExceptionResponse.SUCCESS_SC, content = { From abd507b0d7cb10b9ac60f8b21c3368737204c19b Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 28 May 2025 17:51:42 +0530 Subject: [PATCH 02/13] Introductory Call Allocation Changes --- .../call_conf_allocation/CallAllocationImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/iemr/ecd/service/call_conf_allocation/CallAllocationImpl.java b/src/main/java/com/iemr/ecd/service/call_conf_allocation/CallAllocationImpl.java index 219cee0..f008a26 100644 --- a/src/main/java/com/iemr/ecd/service/call_conf_allocation/CallAllocationImpl.java +++ b/src/main/java/com/iemr/ecd/service/call_conf_allocation/CallAllocationImpl.java @@ -123,6 +123,7 @@ private String allocateMotherRecordsAssociates(RequestCallAllocationDTO callAllo int callCountPointer = 0; if (callAllocationDto.getPreferredLanguage() != null) { + List motherIds = new ArrayList<>(); Page page = outboundCallsRepo.getMotherRecordsForAssociate( PageRequest.of(0, totalRecordToAllocate), "unallocated", @@ -143,11 +144,13 @@ private String allocateMotherRecordsAssociates(RequestCallAllocationDTO callAllo call.setAllocatedUserId(callAllocationDto.getToUserIds()[callCountPointer / callAllocationDto.getNoOfCalls()]); call.setCallAttemptNo(0); callCountPointer++; + motherIds.add(call.getMotherId()); } catch (Exception e) { callCountPointer++; } } outboundCallsRepo.saveAll(outBoundCallList); + motherRecordRepo.updateIsAllocatedStatus(motherIds); } else { List motherRecords = motherRecordRepo.getMotherRecordForAllocation( fromDate, toDate, callAllocationDto.getPhoneNoType(), totalRecordToAllocate); @@ -278,11 +281,11 @@ private String allocateChildRecordsAssociates(RequestCallAllocationDTO callAlloc } } outboundCallsRepo.saveAll(outBoundCallList); - int i = childRecordRepo.updateIsAllocatedStatus(childIds); + childRecordRepo.updateIsAllocatedStatus(childIds); } else if (null != childRecordsForAssociate && !childRecordsForAssociate.isEmpty()) { outBoundCallList = childRecordsForAssociate.getContent(); - + List childIds = new ArrayList<>(); if (!outBoundCallList.isEmpty()) { for (OutboundCalls outboundCall : outBoundCallList) { try { @@ -291,13 +294,14 @@ private String allocateChildRecordsAssociates(RequestCallAllocationDTO callAlloc / callAllocationDto.getNoOfCalls()]); outboundCall.setCallAttemptNo(0); - callCountPointer++; + childIds.add(outboundCall.getChildId()); } catch (Exception e) { callCountPointer++; } } outboundCallsRepo.saveAll(outBoundCallList); + childRecordRepo.updateIsAllocatedStatus(childIds); } } else { throw new ECDException("no eligible record available to allocate, please contact administrator"); From ca91a8998fdf041b8895a56876327a235044331a Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 28 May 2025 21:36:26 +0530 Subject: [PATCH 03/13] fix(bug) :Isfurthercallrequired update fix --- .../service/associate/CallClosureImpl.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/iemr/ecd/service/associate/CallClosureImpl.java b/src/main/java/com/iemr/ecd/service/associate/CallClosureImpl.java index 27462c8..e1719b0 100644 --- a/src/main/java/com/iemr/ecd/service/associate/CallClosureImpl.java +++ b/src/main/java/com/iemr/ecd/service/associate/CallClosureImpl.java @@ -192,9 +192,12 @@ public String closeCall(CallClosureDTO request) { } } - if (obj.getReceivedRoleName() != null && obj.getReceivedRoleName().equalsIgnoreCase(Constants.ANM) - && request.getPreferredLanguage() != null && !request.getPreferredLanguage().isEmpty()) { + isLanguageMapped = isLanguageMappedWithUser(request); + + if (obj.getReceivedRoleName() != null && (obj.getReceivedRoleName().equalsIgnoreCase(Constants.ANM) || callObj.getEcdCallType().equalsIgnoreCase("introductory")) + && request.getPreferredLanguage() != null && !request.getPreferredLanguage().isEmpty() && !isLanguageMapped) { callObj.setAllocationStatus(Constants.UNALLOCATED); + callObj.setCallStatus(Constants.OPEN); callObj.setAllocatedUserId(null); callObj.setCallAttemptNo(0); } else { @@ -203,14 +206,7 @@ public String closeCall(CallClosureDTO request) { if(null != request.getReasonForCallNotAnswered() && Constants.REASONFORCALLNOTANSWERED.contains(request.getReasonForCallNotAnswered()) && !isMaxcallsAttempted) { callObj.setCallStatus(Constants.OPEN); } - isLanguageMapped = isLanguageMappedWithUser(request); - if(!isLanguageMapped && (callObj.getEcdCallType().equalsIgnoreCase("introductory") || callObj.getEcdCallType().equalsIgnoreCase("ANM"))) { - - callObj.setAllocatedUserId(null); - callObj.setCallStatus(Constants.OPEN); - callObj.setCallAttemptNo(0); - callObj.setAllocationStatus(Constants.UNALLOCATED); - } + if (request.getIsHrp() != null) { boolean isHrp = request.getIsHrp(); callObj.setIsHighRisk(isHrp); @@ -244,9 +240,8 @@ else if (!isHrp && !obj.getIsCallDisconnected()) { // Mother outboundCallsRepo.updateHRPForUpcomingCall(callObj.getMotherId(), callObj.getIsHighRisk()); } - } - if (null != obj.getIsFurtherCallRequired() && !obj.getIsFurtherCallRequired()) { + if (null != obj.getIsFurtherCallRequired()) { if (callObj.getMotherId() != null && callObj.getChildId() != null) { try { outboundCallsRepo.updateIsFurtherCallRequiredForUpcomingCallForChild(callObj.getChildId(), From 58a9826837cd25443f2fe68a6b5850e42554098c Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Fri, 30 May 2025 11:08:44 +0530 Subject: [PATCH 04/13] Unallocate Change --- .../call_conf_allocation/CallAllocationImpl.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/main/java/com/iemr/ecd/service/call_conf_allocation/CallAllocationImpl.java b/src/main/java/com/iemr/ecd/service/call_conf_allocation/CallAllocationImpl.java index f008a26..3be17fa 100644 --- a/src/main/java/com/iemr/ecd/service/call_conf_allocation/CallAllocationImpl.java +++ b/src/main/java/com/iemr/ecd/service/call_conf_allocation/CallAllocationImpl.java @@ -618,11 +618,6 @@ public String moveAllocatedCallsToBin(RequestCallAllocationDTO callAllocationDto } else if (callAllocationDto.getRoleName().equalsIgnoreCase("associate")) { outboundCallsPage = getOutboundCallsForMotherAssociate(pageable, callAllocationDto, tempFDateStamp, tempTDateStamp); - } else if (callAllocationDto.getRoleName().equalsIgnoreCase("MO")) { - // MO Role logic for Mother - outboundCallsPage = outboundCallsRepo.getAllocatedRecordsUserByRecordTypeAndPhoneTypeMotherMO( - pageable, callAllocationDto.getUserId(), "open", callAllocationDto.getPhoneNoType(), - tempFDateStamp, tempTDateStamp, callAllocationDto.getPreferredLanguage()); } else { outboundCallsPage = outboundCallsRepo.getAllocatedRecordsUserByRecordTypeAndPhoneTypeMother( pageable, callAllocationDto.getUserId(), "open", callAllocationDto.getPhoneNoType(), @@ -639,12 +634,7 @@ else if (callAllocationDto.getRecordType().equalsIgnoreCase("Child")) { tempFDateStamp, tempTDateStamp, callAllocationDto.getPreferredLanguage()); } else if (callAllocationDto.getRoleName().equalsIgnoreCase("associate")) { outboundCallsPage = getOutboundcallsForChildAssociate(pageable, callAllocationDto, tempFDateStamp, tempTDateStamp); - } else if (callAllocationDto.getRoleName().equalsIgnoreCase("MO")) { - // MO Role logic for Child - outboundCallsPage = outboundCallsRepo.getAllocatedRecordsUserByRecordTypeAndPhoneTypeChildMO( - pageable, callAllocationDto.getUserId(), "open", callAllocationDto.getPhoneNoType(), - tempFDateStamp, tempTDateStamp, callAllocationDto.getPreferredLanguage()); - } else { + } else { outboundCallsPage = outboundCallsRepo.getAllocatedRecordsUserByRecordTypeAndPhoneTypeChild( pageable, callAllocationDto.getUserId(), "open", callAllocationDto.getPhoneNoType(), tempFDateStamp, tempTDateStamp); From ee52ddf1debdc21d391f53a224aca8794beb6b87 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Tue, 10 Jun 2025 12:03:05 +0530 Subject: [PATCH 05/13] AMM-1239 --- pom.xml | 4 + .../dataupload/DataUploadController.java | 2 + .../iemr/ecd/repository/masters/RoleRepo.java | 5 +- .../service/masters/MasterServiceImpl.java | 4 + .../CustomAccessDeniedHandler.java | 22 ++++++ .../CustomAuthenticationEntryPoint.java | 23 ++++++ .../com/iemr/ecd/utils/mapper/JwtUtil.java | 2 +- .../mapper/RoleAuthenticationFilter.java | 73 +++++++++++++++++++ .../iemr/ecd/utils/mapper/SecurityConfig.java | 47 ++++++++++++ .../iemr/ecd/utils/redis/RedisStorage.java | 13 ++++ 10 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java create mode 100644 src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java create mode 100644 src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java create mode 100644 src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java diff --git a/pom.xml b/pom.xml index 186ddfe..fb8ac76 100644 --- a/pom.xml +++ b/pom.xml @@ -245,6 +245,10 @@ 0.12.6 runtime + + org.springframework.boot + spring-boot-starter-security + diff --git a/src/main/java/com/iemr/ecd/controller/dataupload/DataUploadController.java b/src/main/java/com/iemr/ecd/controller/dataupload/DataUploadController.java index e65d229..21e5ddf 100644 --- a/src/main/java/com/iemr/ecd/controller/dataupload/DataUploadController.java +++ b/src/main/java/com/iemr/ecd/controller/dataupload/DataUploadController.java @@ -25,6 +25,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -42,6 +43,7 @@ @RestController @CrossOrigin() +@PreAuthorize("hasRole('SUPERVISOR')") public class DataUploadController { @Autowired diff --git a/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java b/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java index d6e3d64..0e2f6bc 100644 --- a/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java +++ b/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java @@ -23,7 +23,9 @@ import java.util.List; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.iemr.ecd.dao.masters.Role; @@ -32,5 +34,6 @@ public interface RoleRepo extends CrudRepository { List findByPsmIdAndDeleted(Integer psmId, Boolean deleted); - + @Query(nativeQuery = true,value = "select rolename from m_role where roleid in (select roleid from m_userservicerolemapping where userid=:userID)") + String getRoleNamebyUserId(@Param("userID") Long userID); } diff --git a/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java b/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java index 54042fe..666e64a 100644 --- a/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java +++ b/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java @@ -228,6 +228,10 @@ public List getFrequency() { public List getLanguageByUserId(Integer userId) throws ECDException { return v_GetUserlangmappingRepo.findByUserId(userId); } + public String getUserRole(Long userId) { + return roleRepo.getRoleNamebyUserId(userId); + + } //gender master diff --git a/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java new file mode 100644 index 0000000..2f232f2 --- /dev/null +++ b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java @@ -0,0 +1,22 @@ +package com.iemr.ecd.utils.advice.exception_handler; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 + response.setContentType("application/json"); + response.getWriter().write("{\"error\": \"Forbidden\", \"message\": \"" + accessDeniedException.getMessage() + "\"}"); + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..85488ae --- /dev/null +++ b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java @@ -0,0 +1,23 @@ +package com.iemr.ecd.utils.advice.exception_handler; + +import java.io.IOException; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) throws IOException { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 + response.setContentType("application/json"); + response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"" + authException.getMessage() + "\"}"); + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java b/src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java index 719e259..e0b9738 100644 --- a/src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java +++ b/src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java @@ -59,7 +59,7 @@ public T extractClaim(String token, Function claimsResolver) { return claims != null ? claimsResolver.apply(claims) : null; } - private Claims extractAllClaims(String token) { + public Claims extractAllClaims(String token) { return Jwts.parser() .verifyWith(getSigningKey()) .build() diff --git a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java new file mode 100644 index 0000000..5f40e1f --- /dev/null +++ b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java @@ -0,0 +1,73 @@ +package com.iemr.ecd.utils.mapper; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.iemr.ecd.service.masters.MasterServiceImpl; +import com.iemr.ecd.utils.constants.Constants; +import com.iemr.ecd.utils.redis.RedisStorage; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.io.IOException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +@Component +public class RoleAuthenticationFilter extends OncePerRequestFilter { + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private RedisStorage redisService; + + @Autowired + private MasterServiceImpl userService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException, java.io.IOException { + String jwtFromCookie = getJwtTokenFromCookies(request); + String jwtFromHeader = request.getHeader(Constants.JWT_TOKEN); + + String jwtToken = jwtFromCookie != null ? jwtFromCookie : jwtFromHeader; + Claims extractAllClaims = jwtUtil.extractAllClaims(jwtToken); + + String userId = (String) extractAllClaims.get("userId"); + + String authRole = redisService.getUserRoleFromCache(Long.valueOf(userId)); + if (authRole == null) { + String role = userService.getUserRole(Long.valueOf(userId)); + authRole = "ROLE_"+role.toUpperCase(); + redisService.cacheUserRole(Long.valueOf(userId), authRole); + } + + List authorities = List.of(new SimpleGrantedAuthority(authRole)); + + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userId, null, authorities); + SecurityContextHolder.getContext().setAuthentication(auth); + + + filterChain.doFilter(request, response); +} + private String getJwtTokenFromCookies(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equalsIgnoreCase(Constants.JWT_TOKEN)) { + return cookie.getValue(); + } + } + } + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java new file mode 100644 index 0000000..a58b550 --- /dev/null +++ b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java @@ -0,0 +1,47 @@ +package com.iemr.ecd.utils.mapper; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import com.iemr.ecd.utils.advice.exception_handler.CustomAccessDeniedHandler; +import com.iemr.ecd.utils.advice.exception_handler.CustomAuthenticationEntryPoint; + +@Configuration +@EnableMethodSecurity +@EnableWebSecurity +public class SecurityConfig { + private final RoleAuthenticationFilter roleAuthenticationFilter; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + private final CustomAccessDeniedHandler customAccessDeniedHandler; + + public SecurityConfig(RoleAuthenticationFilter roleAuthenticationFilter, + CustomAuthenticationEntryPoint customAuthenticationEntryPoint, + CustomAccessDeniedHandler customAccessDeniedHandler) { + this.roleAuthenticationFilter = roleAuthenticationFilter; + this.customAuthenticationEntryPoint = customAuthenticationEntryPoint; + this.customAccessDeniedHandler = customAccessDeniedHandler; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/user/**").permitAll() + .anyRequest().authenticated() + ).exceptionHandling(ex -> ex + .authenticationEntryPoint(customAuthenticationEntryPoint) + .accessDeniedHandler(customAccessDeniedHandler) + ) + .addFilterBefore(roleAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java index d795ad7..6397b97 100644 --- a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java +++ b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java @@ -21,6 +21,8 @@ */ package com.iemr.ecd.utils.redis; +import java.time.Duration; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +30,7 @@ import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisStringCommands.SetOption; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.types.Expiration; import org.springframework.stereotype.Component; @@ -94,4 +97,14 @@ public void updateConcurrentSessionObject(String value) { } } + @Autowired + private RedisTemplate redisTemplate; + + public void cacheUserRole(Long userId, String role) { + redisTemplate.opsForValue().set("role:" + userId, role, Duration.ofHours(1)); + } + + public String getUserRoleFromCache(Long userId) { + return redisTemplate.opsForValue().get("role:" + userId); + } } From 9e395ed1757392ec75a6d8884aaa2d85e26ac721 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Tue, 10 Jun 2025 12:22:33 +0530 Subject: [PATCH 06/13] Code rabbit comments addressed --- src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java index a58b550..76d856f 100644 --- a/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java +++ b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java @@ -8,6 +8,7 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import com.iemr.ecd.utils.advice.exception_handler.CustomAccessDeniedHandler; import com.iemr.ecd.utils.advice.exception_handler.CustomAuthenticationEntryPoint; @@ -31,10 +32,10 @@ public SecurityConfig(RoleAuthenticationFilter roleAuthenticationFilter, @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(csrf -> csrf.disable()) + .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/user/**").permitAll() + .requestMatchers("/user/*").permitAll() .anyRequest().authenticated() ).exceptionHandling(ex -> ex .authenticationEntryPoint(customAuthenticationEntryPoint) From 9917e0f56a64638dbd9c81f946221c9444282df6 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Tue, 10 Jun 2025 13:19:52 +0530 Subject: [PATCH 07/13] Code rabbit comments --- .../service/masters/MasterServiceImpl.java | 15 ++++- .../CustomAccessDeniedHandler.java | 8 ++- .../mapper/RoleAuthenticationFilter.java | 60 ++++++++++++------- .../iemr/ecd/utils/redis/RedisStorage.java | 18 ++++-- 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java b/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java index 666e64a..7789807 100644 --- a/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java +++ b/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java @@ -228,9 +228,20 @@ public List getFrequency() { public List getLanguageByUserId(Integer userId) throws ECDException { return v_GetUserlangmappingRepo.findByUserId(userId); } + public String getUserRole(Long userId) { - return roleRepo.getRoleNamebyUserId(userId); - + if (null == userId || userId <= 0) { + throw new IllegalArgumentException("Invalid User ID : " + userId); + } + try { + String role = roleRepo.getRoleNamebyUserId(userId); + if (null == role || role.trim().isEmpty()) { + throw new ECDException("No role found for userId : " + userId); + } + return role; + } catch (Exception e) { + throw new ECDException("Failed to retrieverole for usedId : " + userId + " error : " + e.getMessage()); + } } diff --git a/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java index 2f232f2..c3fde93 100644 --- a/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java +++ b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java @@ -6,17 +6,23 @@ import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.io.IOException; +import java.util.Map; @Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { + private static final ObjectMapper mapper = new ObjectMapper(); @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 response.setContentType("application/json"); - response.getWriter().write("{\"error\": \"Forbidden\", \"message\": \"" + accessDeniedException.getMessage() + "\"}"); + Map errorResponse = Map.of("error" , "Forbidden", + "message","Access denied"); + response.getWriter().write(mapper.writeValueAsString(errorResponse)); } } \ No newline at end of file diff --git a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java index 5f40e1f..bbeffc4 100644 --- a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java +++ b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java @@ -2,6 +2,8 @@ import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; @@ -23,6 +25,8 @@ import jakarta.servlet.http.HttpServletResponse; @Component public class RoleAuthenticationFilter extends OncePerRequestFilter { + Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); + @Autowired private JwtUtil jwtUtil; @@ -32,32 +36,46 @@ public class RoleAuthenticationFilter extends OncePerRequestFilter { @Autowired private MasterServiceImpl userService; - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException, java.io.IOException { - String jwtFromCookie = getJwtTokenFromCookies(request); - String jwtFromHeader = request.getHeader(Constants.JWT_TOKEN); + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException, java.io.IOException { + try { + String jwtFromCookie = getJwtTokenFromCookies(request); + String jwtFromHeader = request.getHeader(Constants.JWT_TOKEN); - String jwtToken = jwtFromCookie != null ? jwtFromCookie : jwtFromHeader; - Claims extractAllClaims = jwtUtil.extractAllClaims(jwtToken); - - String userId = (String) extractAllClaims.get("userId"); + String jwtToken = jwtFromCookie != null ? jwtFromCookie : jwtFromHeader; + if(null == jwtToken || jwtToken.trim().isEmpty()) { + filterChain.doFilter(request, response); + return; + } + Claims extractAllClaims = jwtUtil.extractAllClaims(jwtToken); + if(null == extractAllClaims) { + filterChain.doFilter(request, response); + return; + } + Object userIdObj = extractAllClaims.get("userId"); + String userId = userIdObj != null ? userIdObj.toString() : null; - String authRole = redisService.getUserRoleFromCache(Long.valueOf(userId)); - if (authRole == null) { - String role = userService.getUserRole(Long.valueOf(userId)); - authRole = "ROLE_"+role.toUpperCase(); - redisService.cacheUserRole(Long.valueOf(userId), authRole); - } + String authRole = redisService.getUserRoleFromCache(Long.valueOf(userId)); + if (authRole == null) { + String role = userService.getUserRole(Long.valueOf(userId)); + authRole = "ROLE_" + role.toUpperCase(); + redisService.cacheUserRole(Long.valueOf(userId), authRole); + } - List authorities = List.of(new SimpleGrantedAuthority(authRole)); + List authorities = List.of(new SimpleGrantedAuthority(authRole)); - UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userId, null, authorities); - SecurityContextHolder.getContext().setAuthentication(auth); - + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userId, null, + authorities); + SecurityContextHolder.getContext().setAuthentication(auth); + } catch (Exception e) { + logger.error("Authentication filter error for request {}: {}", request.getRequestURI(), e.getMessage()); + SecurityContextHolder.clearContext(); + } finally { + filterChain.doFilter(request, response); + } - filterChain.doFilter(request, response); -} + } private String getJwtTokenFromCookies(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies != null) { diff --git a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java index 6397b97..f6706ad 100644 --- a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java +++ b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java @@ -101,10 +101,20 @@ public void updateConcurrentSessionObject(String value) { private RedisTemplate redisTemplate; public void cacheUserRole(Long userId, String role) { - redisTemplate.opsForValue().set("role:" + userId, role, Duration.ofHours(1)); + try { + redisTemplate.opsForValue().set("role:" + userId, role, Duration.ofHours(1)); + }catch (Exception e) { + logger.warn("Failed to cache role for user {} : {} ", userId, e.getMessage()); + } + } - public String getUserRoleFromCache(Long userId) { - return redisTemplate.opsForValue().get("role:" + userId); - } + public String getUserRoleFromCache(Long userId) { + try { + return redisTemplate.opsForValue().get("role:" + userId); + } catch (Exception e) { + logger.warn("Failed to retrieve cached role for user {} : {} ", userId, e.getMessage()); + return null; + } + } } From 20542c6a74499c4f5388396766f4729e1e8afc8c Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 11 Jun 2025 10:01:40 +0530 Subject: [PATCH 08/13] review comments updated --- .../ecd/utils/mapper/RoleAuthenticationFilter.java | 14 +------------- .../com/iemr/ecd/utils/redis/RedisStorage.java | 14 +++++++------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java index bbeffc4..ef42529 100644 --- a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java +++ b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java @@ -40,7 +40,7 @@ public class RoleAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, java.io.IOException { try { - String jwtFromCookie = getJwtTokenFromCookies(request); + String jwtFromCookie = CookieUtil.getJwtTokenFromCookie(request); String jwtFromHeader = request.getHeader(Constants.JWT_TOKEN); String jwtToken = jwtFromCookie != null ? jwtFromCookie : jwtFromHeader; @@ -76,16 +76,4 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } } - private String getJwtTokenFromCookies(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equalsIgnoreCase(Constants.JWT_TOKEN)) { - return cookie.getValue(); - } - } - } - return null; - } - } \ No newline at end of file diff --git a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java index f6706ad..a0b5adb 100644 --- a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java +++ b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java @@ -100,14 +100,14 @@ public void updateConcurrentSessionObject(String value) { @Autowired private RedisTemplate redisTemplate; - public void cacheUserRole(Long userId, String role) { - try { - redisTemplate.opsForValue().set("role:" + userId, role, Duration.ofHours(1)); - }catch (Exception e) { - logger.warn("Failed to cache role for user {} : {} ", userId, e.getMessage()); + public void cacheUserRole(Long userId, String role) { + try { + redisTemplate.opsForValue().set("role:" + userId, role, Duration.ofHours(1)); + } catch (Exception e) { + logger.warn("Failed to cache role for user {} : {} ", userId, e.getMessage()); } - - } + + } public String getUserRoleFromCache(Long userId) { try { From d1cec3820b7ce3d6ecc244230e66387085b65234 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Fri, 4 Jul 2025 10:11:27 +0530 Subject: [PATCH 09/13] @PreAuthorize("hasRole('SUPERVISOR')") Added for EcdQuestionareController --- .../ecd/controller/questionare/EcdQuestionareController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/iemr/ecd/controller/questionare/EcdQuestionareController.java b/src/main/java/com/iemr/ecd/controller/questionare/EcdQuestionareController.java index d359959..c80ecae 100644 --- a/src/main/java/com/iemr/ecd/controller/questionare/EcdQuestionareController.java +++ b/src/main/java/com/iemr/ecd/controller/questionare/EcdQuestionareController.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -57,6 +58,7 @@ @RestController @RequestMapping(value = "/Questionnaire", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('SUPERVISOR')") public class EcdQuestionareController { @Autowired From a2bc6240efb9e41dfc19858d136acf775758d0bc Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Mon, 7 Jul 2025 09:56:50 +0530 Subject: [PATCH 10/13] Role based Authentication --- .../dataupload/DataTemplateController.java | 2 ++ .../CallStatisticsController.java | 2 ++ .../OutBoundWorklistController.java | 2 ++ .../controller/quality/ChartsController.java | 2 ++ .../quality/GradeConfigurationController.java | 2 ++ .../quality/QualityAuditController.java | 2 ++ ...yAuditQuestionConfigurationController.java | 2 ++ ...tyAuditSectionConfigurationController.java | 2 ++ ...ampleSelectionConfigurationController.java | 2 ++ .../iemr/ecd/repository/masters/RoleRepo.java | 2 +- .../service/masters/MasterServiceImpl.java | 6 ++--- .../mapper/RoleAuthenticationFilter.java | 24 ++++++++++++------- .../iemr/ecd/utils/redis/RedisStorage.java | 11 +++++---- 13 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/iemr/ecd/controller/dataupload/DataTemplateController.java b/src/main/java/com/iemr/ecd/controller/dataupload/DataTemplateController.java index b7718b0..725643e 100644 --- a/src/main/java/com/iemr/ecd/controller/dataupload/DataTemplateController.java +++ b/src/main/java/com/iemr/ecd/controller/dataupload/DataTemplateController.java @@ -29,6 +29,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -49,6 +50,7 @@ @RestController @RequestMapping(value = "/dataTemplate", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('SUPERVISOR')") public class DataTemplateController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/outboundworklist/CallStatisticsController.java b/src/main/java/com/iemr/ecd/controller/outboundworklist/CallStatisticsController.java index 892cf53..22e88eb 100644 --- a/src/main/java/com/iemr/ecd/controller/outboundworklist/CallStatisticsController.java +++ b/src/main/java/com/iemr/ecd/controller/outboundworklist/CallStatisticsController.java @@ -25,6 +25,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -43,6 +44,7 @@ @RestController @RequestMapping(value = "/agent", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('SUPERVISOR') || hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class CallStatisticsController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/outboundworklist/OutBoundWorklistController.java b/src/main/java/com/iemr/ecd/controller/outboundworklist/OutBoundWorklistController.java index 0465259..7bedbaf 100644 --- a/src/main/java/com/iemr/ecd/controller/outboundworklist/OutBoundWorklistController.java +++ b/src/main/java/com/iemr/ecd/controller/outboundworklist/OutBoundWorklistController.java @@ -28,6 +28,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -49,6 +50,7 @@ @RestController @RequestMapping(value = "/outbound-worklist", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('ANM') || hasRole('MO') || hasRole('ASSOCIATE')") public class OutBoundWorklistController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/quality/ChartsController.java b/src/main/java/com/iemr/ecd/controller/quality/ChartsController.java index d11aa8f..5b0f505 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/ChartsController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/ChartsController.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -45,6 +46,7 @@ @RestController @RequestMapping(value = "/charts", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('SUPERVISOR') || hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class ChartsController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/quality/GradeConfigurationController.java b/src/main/java/com/iemr/ecd/controller/quality/GradeConfigurationController.java index 158f60b..752e1e4 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/GradeConfigurationController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/GradeConfigurationController.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -48,6 +49,7 @@ @RestController @RequestMapping(value = "/gradeConfiguration", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('QUALITY_SUPERVISOR')") public class GradeConfigurationController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditController.java b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditController.java index b0575b2..4ddecda 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditController.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -56,6 +57,7 @@ @RestController @RequestMapping(value = "/qualityAudit", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('QUALITY_AUDITOR')") public class QualityAuditController { @Autowired private QualityAuditImpl qualityAuditImpl; diff --git a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditQuestionConfigurationController.java b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditQuestionConfigurationController.java index 37afddb..7695ca7 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditQuestionConfigurationController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditQuestionConfigurationController.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -49,6 +50,7 @@ @RestController @RequestMapping(value = "/questionnaireConfiguration", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class QualityAuditQuestionConfigurationController { @Autowired private QualityAuditQuestionConfigurationImpl qualityAuditQuestionConfigurationImpl; diff --git a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditSectionConfigurationController.java b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditSectionConfigurationController.java index 249eb91..1a0f7bf 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditSectionConfigurationController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditSectionConfigurationController.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -48,6 +49,7 @@ @RestController @RequestMapping(value = "/sectionConfiguration", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class QualityAuditSectionConfigurationController { @Autowired private QualityAuditSectionConfigurationImpl qualityAuditSectionConfigurationImpl; diff --git a/src/main/java/com/iemr/ecd/controller/quality/SampleSelectionConfigurationController.java b/src/main/java/com/iemr/ecd/controller/quality/SampleSelectionConfigurationController.java index b117ee0..d3cb949 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/SampleSelectionConfigurationController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/SampleSelectionConfigurationController.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -48,6 +49,7 @@ @RestController @RequestMapping(value = "/sampleSelectionConfiguration", headers = "Authorization") @CrossOrigin() +@PreAuthorize("hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class SampleSelectionConfigurationController { @Autowired private SampleSelectionConfigurationImpl sampleSelectionConfigurationImpl; diff --git a/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java b/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java index 0e2f6bc..9351d4a 100644 --- a/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java +++ b/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java @@ -35,5 +35,5 @@ public interface RoleRepo extends CrudRepository { List findByPsmIdAndDeleted(Integer psmId, Boolean deleted); @Query(nativeQuery = true,value = "select rolename from m_role where roleid in (select roleid from m_userservicerolemapping where userid=:userID)") - String getRoleNamebyUserId(@Param("userID") Long userID); + List getRoleNamebyUserId(@Param("userID") Long userID); } diff --git a/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java b/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java index 7789807..2206dd5 100644 --- a/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java +++ b/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java @@ -229,13 +229,13 @@ public List getLanguageByUserId(Integer userId) throws ECD return v_GetUserlangmappingRepo.findByUserId(userId); } - public String getUserRole(Long userId) { + public List getUserRoles(Long userId) { if (null == userId || userId <= 0) { throw new IllegalArgumentException("Invalid User ID : " + userId); } try { - String role = roleRepo.getRoleNamebyUserId(userId); - if (null == role || role.trim().isEmpty()) { + List role = roleRepo.getRoleNamebyUserId(userId); + if (null == role || role.isEmpty()) { throw new ECDException("No role found for userId : " + userId); } return role; diff --git a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java index ef42529..aa19cbe 100644 --- a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java +++ b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java @@ -1,6 +1,8 @@ package com.iemr.ecd.utils.mapper; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +41,7 @@ public class RoleAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, java.io.IOException { + List authRoles = null; try { String jwtFromCookie = CookieUtil.getJwtTokenFromCookie(request); String jwtFromHeader = request.getHeader(Constants.JWT_TOKEN); @@ -56,17 +59,22 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse Object userIdObj = extractAllClaims.get("userId"); String userId = userIdObj != null ? userIdObj.toString() : null; - String authRole = redisService.getUserRoleFromCache(Long.valueOf(userId)); - if (authRole == null) { - String role = userService.getUserRole(Long.valueOf(userId)); - authRole = "ROLE_" + role.toUpperCase(); - redisService.cacheUserRole(Long.valueOf(userId), authRole); + authRoles = redisService.getUserRoleFromCache(Long.valueOf(userId)); + if (authRoles == null || authRoles.isEmpty()) { + List roles = userService.getUserRoles(Long.valueOf(userId)); // assuming this returns multiple roles + authRoles = roles.stream() + .filter(Objects::nonNull) + .map(String::trim) + .map(role -> "ROLE_" + role.toUpperCase().replace(" ", "_")) + .collect(Collectors.toList()); + redisService.cacheUserRoles(Long.valueOf(userId), authRoles); } - List authorities = List.of(new SimpleGrantedAuthority(authRole)); + List authorities = authRoles.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); - UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userId, null, - authorities); + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userId, null, authorities); SecurityContextHolder.getContext().setAuthentication(auth); } catch (Exception e) { logger.error("Authentication filter error for request {}: {}", request.getRequestURI(), e.getMessage()); diff --git a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java index a0b5adb..99370de 100644 --- a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java +++ b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java @@ -22,6 +22,7 @@ package com.iemr.ecd.utils.redis; import java.time.Duration; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -100,18 +101,20 @@ public void updateConcurrentSessionObject(String value) { @Autowired private RedisTemplate redisTemplate; - public void cacheUserRole(Long userId, String role) { + public void cacheUserRoles(Long userId, List roles) { try { - redisTemplate.opsForValue().set("role:" + userId, role, Duration.ofHours(1)); + String key = "roles:" + userId; + redisTemplate.delete(key); // Clear previous cache + redisTemplate.opsForList().rightPushAll(key, roles); } catch (Exception e) { logger.warn("Failed to cache role for user {} : {} ", userId, e.getMessage()); } } - public String getUserRoleFromCache(Long userId) { + public List getUserRoleFromCache(Long userId) { try { - return redisTemplate.opsForValue().get("role:" + userId); + return redisTemplate.opsForList().range("roles:" + userId, 0, -1); } catch (Exception e) { logger.warn("Failed to retrieve cached role for user {} : {} ", userId, e.getMessage()); return null; From 9d7475483feb2f4385440eb5c2fb3a03a7e80c17 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Mon, 7 Jul 2025 14:17:30 +0530 Subject: [PATCH 11/13] Security Hotspot fix --- .../iemr/ecd/utils/mapper/SecurityConfig.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java index 76d856f..0a5dab4 100644 --- a/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java +++ b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java @@ -29,20 +29,24 @@ public SecurityConfig(RoleAuthenticationFilter roleAuthenticationFilter, this.customAccessDeniedHandler = customAccessDeniedHandler; } - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http - .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> auth - .requestMatchers("/user/*").permitAll() - .anyRequest().authenticated() - ).exceptionHandling(ex -> ex - .authenticationEntryPoint(customAuthenticationEntryPoint) - .accessDeniedHandler(customAccessDeniedHandler) - ) - .addFilterBefore(roleAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); + csrfTokenRepository.setCookieHttpOnly(true); // Fixes the security hotspot - return http.build(); - } + http + .csrf(csrf -> csrf.csrfTokenRepository(csrfTokenRepository)) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/user/*").permitAll() + .anyRequest().authenticated() + ) + .exceptionHandling(ex -> ex + .authenticationEntryPoint(customAuthenticationEntryPoint) + .accessDeniedHandler(customAccessDeniedHandler) + ) + .addFilterBefore(roleAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } } \ No newline at end of file From 9be3aa1cbe386488e51decef48eec4d179c13c03 Mon Sep 17 00:00:00 2001 From: ravishanigarapu <133210792+ravishanigarapu@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:39:59 +0530 Subject: [PATCH 12/13] Update SecurityConfig.java --- .../iemr/ecd/utils/mapper/SecurityConfig.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java index 0a5dab4..31409fa 100644 --- a/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java +++ b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java @@ -29,24 +29,24 @@ public SecurityConfig(RoleAuthenticationFilter roleAuthenticationFilter, this.customAccessDeniedHandler = customAccessDeniedHandler; } - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); - csrfTokenRepository.setCookieHttpOnly(true); // Fixes the security hotspot +@Bean +public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + CookieCsrfTokenRepository csrfTokenRepository = new CookieCsrfTokenRepository(); + csrfTokenRepository.setCookieHttpOnly(true); + csrfTokenRepository.setCookiePath("/"); + http + .csrf(csrf -> csrf.csrfTokenRepository(csrfTokenRepository)) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/user/*").permitAll() + .anyRequest().authenticated() + ) + .exceptionHandling(ex -> ex + .authenticationEntryPoint(customAuthenticationEntryPoint) + .accessDeniedHandler(customAccessDeniedHandler) + ) + .addFilterBefore(roleAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - http - .csrf(csrf -> csrf.csrfTokenRepository(csrfTokenRepository)) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> auth - .requestMatchers("/user/*").permitAll() - .anyRequest().authenticated() - ) - .exceptionHandling(ex -> ex - .authenticationEntryPoint(customAuthenticationEntryPoint) - .accessDeniedHandler(customAccessDeniedHandler) - ) - .addFilterBefore(roleAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } -} \ No newline at end of file + return http.build(); +} +} From c690b3cca1a7d94806c8af53b77d0bd11e00856e Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Mon, 7 Jul 2025 15:42:13 +0530 Subject: [PATCH 13/13] Coderabbit comments updated --- .../utils/mapper/RoleAuthenticationFilter.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java index aa19cbe..8fadd88 100644 --- a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java +++ b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java @@ -58,8 +58,19 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } Object userIdObj = extractAllClaims.get("userId"); String userId = userIdObj != null ? userIdObj.toString() : null; - - authRoles = redisService.getUserRoleFromCache(Long.valueOf(userId)); + if (null == userId || userId.trim().isEmpty()) { + filterChain.doFilter(request, response); + return; + } + Long userIdLong; + try { + userIdLong=Long.valueOf(userId); + }catch (NumberFormatException ex) { + logger.warn("Invalid userId format: {}",userId); + filterChain.doFilter(request, response); + return; + } + authRoles = redisService.getUserRoleFromCache(userIdLong); if (authRoles == null || authRoles.isEmpty()) { List roles = userService.getUserRoles(Long.valueOf(userId)); // assuming this returns multiple roles authRoles = roles.stream()