Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -48,6 +49,7 @@

@RestController
@RequestMapping(value = "/dataTemplate", headers = "Authorization")
@PreAuthorize("hasRole('SUPERVISOR')")
public class DataTemplateController {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
Expand All @@ -41,6 +42,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponses;

@RestController
@PreAuthorize("hasRole('SUPERVISOR')")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't follow where this hasRole is checked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please verify In RoleAuthenticationFilter line no 66

public class DataUploadController {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -41,7 +42,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponses;

@RestController
@RequestMapping(value = "/agent", headers = "Authorization")
@PreAuthorize("hasRole('SUPERVISOR') || hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')")
public class CallStatisticsController {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -48,6 +49,7 @@

@RestController
@RequestMapping(value = "/outbound-worklist", headers = "Authorization")
@PreAuthorize("hasRole('ANM') || hasRole('MO') || hasRole('ASSOCIATE')")
public class OutBoundWorklistController {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -44,6 +45,7 @@

@RestController
@RequestMapping(value = "/charts", headers = "Authorization")
@PreAuthorize("hasRole('SUPERVISOR') || hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')")
public class ChartsController {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -47,6 +48,7 @@

@RestController
@RequestMapping(value = "/gradeConfiguration", headers = "Authorization")
@PreAuthorize("hasRole('QUALITY_SUPERVISOR')")
public class GradeConfigurationController {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand Down Expand Up @@ -55,6 +55,7 @@

@RestController
@RequestMapping(value = "/qualityAudit", headers = "Authorization")
@PreAuthorize("hasRole('QUALITY_AUDITOR')")
public class QualityAuditController {
@Autowired
private QualityAuditImpl qualityAuditImpl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -48,6 +49,7 @@

@RestController
@RequestMapping(value = "/questionnaireConfiguration", headers = "Authorization")
@PreAuthorize("hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')")
public class QualityAuditQuestionConfigurationController {
@Autowired
private QualityAuditQuestionConfigurationImpl qualityAuditQuestionConfigurationImpl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -47,6 +48,7 @@

@RestController
@RequestMapping(value = "/sectionConfiguration", headers = "Authorization")
@PreAuthorize("hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')")
public class QualityAuditSectionConfigurationController {
@Autowired
private QualityAuditSectionConfigurationImpl qualityAuditSectionConfigurationImpl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -47,6 +48,7 @@

@RestController
@RequestMapping(value = "/sampleSelectionConfiguration", headers = "Authorization")
@PreAuthorize("hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')")
public class SampleSelectionConfigurationController {
@Autowired
private SampleSelectionConfigurationImpl sampleSelectionConfigurationImpl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand Down Expand Up @@ -56,6 +57,7 @@

@RestController
@RequestMapping(value = "/Questionnaire", headers = "Authorization")
@PreAuthorize("hasRole('SUPERVISOR')")
public class EcdQuestionareController {

@Autowired
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,5 +34,6 @@
public interface RoleRepo extends CrudRepository<Role, Integer> {

List<Role> 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)")
List<String> getRoleNamebyUserId(@Param("userID") Long userID);
}
15 changes: 15 additions & 0 deletions src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,21 @@ public List<Frequency> getFrequency() {
public List<V_GetUserlangmapping> getLanguageByUserId(Integer userId) throws ECDException {
return v_GetUserlangmappingRepo.findByUserId(userId);
}

public List<String> getUserRoles(Long userId) {
if (null == userId || userId <= 0) {
throw new IllegalArgumentException("Invalid User ID : " + userId);
}
try {
List<String> role = roleRepo.getRoleNamebyUserId(userId);
if (null == role || role.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());
}
}


//gender master
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 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");
Map<String, String> errorResponse = Map.of("error" , "Forbidden",
"message","Access denied");
response.getWriter().write(mapper.writeValueAsString(errorResponse));
}
}
Original file line number Diff line number Diff line change
@@ -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() + "\"}");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security vulnerability: Information disclosure and JSON injection risk.

This has the same security issues as CustomAccessDeniedHandler:

  1. Information leakage: Raw exception messages can expose sensitive system details.
  2. JSON injection: Manual JSON construction without escaping is vulnerable to injection attacks.

Apply the same fix pattern:

-        response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"" + authException.getMessage() + "\"}");
+        response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"Authentication required\"}");

Or use a JSON library for proper serialization as suggested in the CustomAccessDeniedHandler review.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"" + authException.getMessage() + "\"}");
response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"Authentication required\"}");
πŸ€– Prompt for AI Agents
In
src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java
at line 21, the code manually constructs a JSON response using raw exception
messages, causing information leakage and JSON injection risks. To fix this,
avoid including raw exception messages directly in the response. Instead, use a
JSON serialization library like Jackson or Gson to build the JSON response
safely, ensuring proper escaping and preventing injection. Also, sanitize or
replace sensitive exception details with generic error messages to avoid
information disclosure.

⚠️ Potential issue

Potential information leakage and JSON injection vulnerability.

Similar to the CustomAccessDeniedHandler, directly including authException.getMessage() in the JSON response could expose sensitive information or cause JSON injection attacks.

Apply the same security fixes as recommended for CustomAccessDeniedHandler:

-response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"" + authException.getMessage() + "\"}");
+response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"Authentication required\"}");

Or use a proper JSON library for safe serialization.

πŸ€– Prompt for AI Agents
In
src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java
at line 21, directly embedding authException.getMessage() in the JSON response
risks information leakage and JSON injection. To fix this, sanitize or escape
the exception message before including it in the JSON output, or better, use a
JSON library like Jackson or Gson to safely serialize the error message,
ensuring no sensitive data is exposed and the JSON structure remains secure.

}
}
2 changes: 1 addition & 1 deletion src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
return claims != null ? claimsResolver.apply(claims) : null;
}

private Claims extractAllClaims(String token) {
public Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
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;
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 {
Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());

@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 {
List<String> authRoles = null;
try {
String jwtFromCookie = CookieUtil.getJwtTokenFromCookie(request);
String jwtFromHeader = request.getHeader(Constants.JWT_TOKEN);

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;
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<String> 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<GrantedAuthority> authorities = authRoles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

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);
}

}
}
Loading
Loading