Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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 @@ -245,6 +245,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 @@ -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;
Expand All @@ -42,6 +43,7 @@

@RestController
@CrossOrigin()
@PreAuthorize("hasRole('SUPERVISOR')")

Copy link
Copy Markdown
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
Copy Markdown
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
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)")
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 String getUserRole(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()) {
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());
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typos in exception message.

The implementation correctly follows the previous suggestions for input validation and error handling. However, there are typos in the exception message that need correction:

-			throw new ECDException("Failed to retrieverole for usedId : " + userId + " error : " + e.getMessage());
+			throw new ECDException("Failed to retrieve role for userId : " + userId + " error : " + e.getMessage());

Additionally, consider preserving the original exception as a cause for better debugging:

-			throw new ECDException("Failed to retrieve role for userId : " + userId + " error : " + e.getMessage());
+			throw new ECDException("Failed to retrieve role for userId : " + userId, e);
πŸ“ 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
public String getUserRole(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()) {
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());
}
}
public String getUserRole(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()) {
throw new ECDException("No role found for userId : " + userId);
}
return role;
} catch (Exception e) {
throw new ECDException("Failed to retrieve role for userId : " + userId, e);
}
}
πŸ€– Prompt for AI Agents
In src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java around
lines 232 to 245, fix the typos in the exception messages by correcting
"retrieverole" to "retrieve role" and "usedId" to "userId". Also, update the
catch block to pass the caught exception as the cause when throwing the new
ECDException to preserve the original stack trace for better debugging.



//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
Copy Markdown

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,91 @@
package com.iemr.ecd.utils.mapper;

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;
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 {
try {
String jwtFromCookie = getJwtTokenFromCookies(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;

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<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(authRole));

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

}
private String getJwtTokenFromCookies(HttpServletRequest request) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this method seems to be appearing in a lot of files. Can't we reuse?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, I have updated

Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equalsIgnoreCase(Constants.JWT_TOKEN)) {
return cookie.getValue();
}
}
}
return null;
}

}
48 changes: 48 additions & 0 deletions src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.iemr.ecd.utils.mapper;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Move configuration class to appropriate package.

Configuration classes should typically be placed in a config package rather than utils.mapper. This improves code organization and follows Spring Boot conventions.

Move this class to com.iemr.ecd.config.SecurityConfig.

πŸ€– Prompt for AI Agents
In src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java at line 1, the
SecurityConfig class is currently placed in the utils.mapper package, which is
not appropriate for configuration classes. Move the SecurityConfig.java file to
the package com.iemr.ecd.config by changing its directory location and updating
the package declaration at the top of the file to com.iemr.ecd.config. This
aligns with Spring Boot conventions and improves project organization.


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 org.springframework.security.web.csrf.CookieCsrfTokenRepository;

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.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

withHttpOnlyFalse
this is creating a security hotspot. please check.

.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/*").permitAll()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does this matching mean?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If the requestURL coming like 'user/userAuhenticate' it will give the permission it will not check the role.

.anyRequest().authenticated()
).exceptionHandling(ex -> ex
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler)
)
.addFilterBefore(roleAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}
}
23 changes: 23 additions & 0 deletions src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
*/
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;
import org.springframework.beans.factory.annotation.Value;
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;

Expand Down Expand Up @@ -94,4 +97,24 @@ public void updateConcurrentSessionObject(String value) {

}
}
@Autowired
private RedisTemplate<String, String> redisTemplate;

public void cacheUserRole(Long userId, String role) {
try {
redisTemplate.opsForValue().set("role:" + userId, role, Duration.ofHours(1));
}catch (Exception e) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

indent/spacing

logger.warn("Failed to cache role for user {} : {} ", userId, e.getMessage());
}

}

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