Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.MediaType;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.iemr.admin.data.employeemaster.M_Community;
import com.iemr.admin.data.employeemaster.M_Designation;
import com.iemr.admin.data.employeemaster.M_Gender;
Expand Down Expand Up @@ -1051,8 +1050,11 @@ public String getEmployeeByDesignation(@RequestBody String getDesignation) {

ArrayList<M_User1> employeeBydesiganation = employeeMasterInter.getEmployeeByDesiganationID(
employeeMaster.getDesignationID(), employeeMaster1.getServiceProviderID());

response.setResponse(employeeBydesiganation.toString());
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation() // Only serialize fields with @Expose
.create();
String json = gson.toJson(employeeBydesiganation);
response.setResponse(json);
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

PII/credential exposure risk in JSON serialization

Using GsonBuilder().excludeFieldsWithoutExposeAnnotation() will serialize all @Expose fields in M_User1, including password, agentPassword, aadhaarNo, pAN (currently annotated). This will leak sensitive data in API responses.

Two safe options:

  • Remove/mark @Expose(serialize = false) on sensitive fields (preferred; see proposed diffs in M_User1).
  • Or locally exclude known-sensitive fields here:
-            Gson gson = new GsonBuilder()
-                    .excludeFieldsWithoutExposeAnnotation()
-                    .create();
+            Gson gson = new GsonBuilder()
+                    .excludeFieldsWithoutExposeAnnotation()
+                    .addSerializationExclusionStrategy(new com.google.gson.ExclusionStrategy() {
+                        public boolean shouldSkipField(com.google.gson.FieldAttributes f) {
+                            String n = f.getName();
+                            return "password".equals(n)
+                                   || "agentPassword".equals(n)
+                                   || "aadhaarNo".equals(n)
+                                   || "pAN".equals(n);
+                        }
+                        public boolean shouldSkipClass(Class<?> c) { return false; }
+                    })
+                    .create();
πŸ“ 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
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation() // Only serialize fields with @Expose
.create();
String json = gson.toJson(employeeBydesiganation);
response.setResponse(json);
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation() // Only serialize fields with @Expose
.addSerializationExclusionStrategy(new com.google.gson.ExclusionStrategy() {
@Override
public boolean shouldSkipField(com.google.gson.FieldAttributes f) {
String n = f.getName();
return "password".equals(n)
|| "agentPassword".equals(n)
|| "aadhaarNo".equals(n)
|| "pAN".equals(n);
}
@Override
public boolean shouldSkipClass(Class<?> c) {
return false;
}
})
.create();
String json = gson.toJson(employeeBydesiganation);
response.setResponse(json);
πŸ€– Prompt for AI Agents
In
src/main/java/com/iemr/admin/controller/employeemaster/EmployeeMasterController.java
around lines 1053-1057, the code uses
GsonBuilder().excludeFieldsWithoutExposeAnnotation() which will still serialize
any @Expose fields (including sensitive ones like password, agentPassword,
aadhaarNo, pAN) and leak PII; fix by either 1) updating the M_User1 model to
mark sensitive fields with @Expose(serialize = false) (preferred) so they are
never serialized, or 2) alter this controller to create a Gson instance with an
ExclusionStrategy (or use GsonBuilder().addSerializationExclusionStrategy(...))
that explicitly excludes those sensitive field names/types before calling
toJson; apply the change consistently and run related API tests to confirm
sensitive fields are omitted from responses.


} catch (Exception e) {
logger.error("Unexpected error:", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/
package com.iemr.admin.controller.employeemaster;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

Expand All @@ -46,6 +47,7 @@
import com.iemr.admin.utils.response.OutputResponse;

import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;


@PropertySource("classpath:application.properties")
Expand Down Expand Up @@ -92,19 +94,25 @@ public ResponseEntity<byte[]> fetchFile(@PathVariable("userID") Long userID) thr

EmployeeSignature userSignID = employeeSignatureServiceImpl.fetchSignature(userID);
HttpHeaders responseHeaders = new HttpHeaders();
ContentDisposition contentDisposition = ContentDisposition.attachment()
.filename(userSignID.getFileName(), StandardCharsets.UTF_8).build();
responseHeaders.setContentDisposition(contentDisposition);
String fileName = URLEncoder.encode(userSignID.getFileName(), StandardCharsets.UTF_8);

responseHeaders.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + fileName);

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

Use RFC-5987-safe filename; avoid URLEncoder in headers

URLEncoder produces + for spaces (form encoding), which is invalid for filename*. Use ContentDisposition builder with charset to set both safely.

Apply this diff:

-            String fileName = URLEncoder.encode(userSignID.getFileName(), StandardCharsets.UTF_8);
-
-            responseHeaders.set(HttpHeaders.CONTENT_DISPOSITION,
-                "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + fileName);
+            ContentDisposition cd = ContentDisposition.attachment()
+                    .filename(userSignID.getFileName(), StandardCharsets.UTF_8)
+                    .build();
+            responseHeaders.setContentDisposition(cd);

Also handle userSignID == null by returning 404.

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In
src/main/java/com/iemr/admin/controller/employeemaster/EmployeeSignatureController.java
around lines 97 to 101, replace the URLEncoder-based header construction and add
a null check: first, if userSignID == null return
ResponseEntity.status(HttpStatus.NOT_FOUND).build(); otherwise build a
ContentDisposition via
ContentDisposition.attachment().filename(userSignID.getFileName(),
StandardCharsets.UTF_8).build() and set it on
responseHeaders.setContentDisposition(contentDisposition) (or
responseHeaders.set(HttpHeaders.CONTENT_DISPOSITION,
contentDisposition.toString())); remove URLEncoder usage so filename* is
RFC-5987-safe and both plain and encoded filename forms are handled by the
ContentDisposition builder.

MediaType mediaType;
try {
mediaType = MediaType.parseMediaType(userSignID.getFileType());
} catch (InvalidMediaTypeException | NullPointerException ex) {
mediaType = MediaType.APPLICATION_OCTET_STREAM;
mediaType = MediaType.parseMediaType(userSignID.getFileType());
} catch (InvalidMediaTypeException | NullPointerException e) {
mediaType = MediaType.APPLICATION_OCTET_STREAM;
}

return ResponseEntity.ok().contentType(mediaType).headers(responseHeaders)
.contentLength(userSignID.getSignature().length).body(userSignID.getSignature());
byte[] fileBytes = userSignID.getSignature(); // MUST be byte[]

return ResponseEntity.ok()
.headers(responseHeaders)
.contentType(mediaType)
.contentLength(fileBytes.length)
.body(fileBytes);

} catch (Exception e) {
logger.error("Unexpected error:", e);
Expand Down Expand Up @@ -135,4 +143,18 @@ public String existFile(@PathVariable("userID") Long userID) throws Exception {
logger.debug("response" + response);
return response.toString();
}
@Operation(summary = "Active or DeActive user Signature")
@RequestMapping(value = "/activateOrdeActivateSignature", method = { RequestMethod.POST }, produces = {
"application/json" })
public String ActivateUser(@RequestBody String activateUser, HttpServletRequest request) {
OutputResponse response = new OutputResponse();
try {
EmployeeSignature empSignature = employeeSignatureServiceImpl.updateUserSignatureStatus(activateUser);
response.setResponse(empSignature.toString());
} catch (Exception e) {
logger.error("Active or Deactivate User Signature failed with exception " + e.getMessage(), e);
response.setError(e);
}
return response.toString();
}
Comment on lines +146 to +153
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

Auth missing and overexposing response

  • Endpoint lacks headers = "Authorization" while other endpoints require it. This allows unauthenticated status toggles.
  • Returning full EmployeeSignature (potentially including BLOB) is heavy and may leak data. Return a minimal DTO.

Apply this diff:

-    @RequestMapping(value = "/activateOrdeActivateSignature", method = { RequestMethod.POST }, produces = {
-            "application/json" })
+    @RequestMapping(value = "/activateOrdeActivateSignature", headers = "Authorization",
+            method = { RequestMethod.POST }, produces = { "application/json" })
     public String ActivateUser(@RequestBody String activateUser, HttpServletRequest request) {
         OutputResponse response = new OutputResponse();
         try {
-            EmployeeSignature empSignature = employeeSignatureServiceImpl.updateUserSignatureStatus(activateUser);
-            response.setResponse(empSignature.toString());
+            EmployeeSignature empSignature = employeeSignatureServiceImpl.updateUserSignatureStatus(activateUser);
+            boolean active = empSignature.getDeleted() == null ? false : !empSignature.getDeleted();
+            response.setResponse("{\"userID\":" + empSignature.getUserID() + ",\"active\":" + active + "}");
         } catch (Exception e) {
             logger.error("Active or Deactivate User Signature failed with exception " + e.getMessage(), e);
             response.setError(e);
         }
         return response.toString();
     }
πŸ“ 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
@Operation(summary = "Active or DeActive user Signature")
@RequestMapping(value = "/activateOrdeActivateSignature", method = { RequestMethod.POST }, produces = {
"application/json" })
public String ActivateUser(@RequestBody String activateUser, HttpServletRequest request) {
OutputResponse response = new OutputResponse();
try {
EmployeeSignature empSignature = employeeSignatureServiceImpl.updateUserSignatureStatus(activateUser);
response.setResponse(empSignature.toString());
} catch (Exception e) {
logger.error("Active or Deactivate User Signature failed with exception " + e.getMessage(), e);
response.setError(e);
}
return response.toString();
}
@Operation(summary = "Active or DeActive user Signature")
@RequestMapping(
value = "/activateOrdeActivateSignature",
headers = "Authorization",
method = { RequestMethod.POST },
produces = { "application/json" }
)
public String ActivateUser(@RequestBody String activateUser, HttpServletRequest request) {
OutputResponse response = new OutputResponse();
try {
EmployeeSignature empSignature =
employeeSignatureServiceImpl.updateUserSignatureStatus(activateUser);
boolean active =
empSignature.getDeleted() == null
? false
: !empSignature.getDeleted();
response.setResponse(
"{\"userID\":"
empSignature.getUserID()
",\"active\":"
active
"}"
);
} catch (Exception e) {
logger.error(
"Active or Deactivate User Signature failed with exception " + e.getMessage(),
e
);
response.setError(e);
}
return response.toString();
}

}
24 changes: 23 additions & 1 deletion src/main/java/com/iemr/admin/data/employeemaster/M_User1.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,32 @@ public class M_User1{
@Expose
private M_Maritalstatus m_Maritalstatus;

@Expose
@Transient
private String signatureStatus;

/*@OneToOne(mappedBy="m_user")
private M_UserLangMapping m_UserLangMapping1;*/

// new field for rate-limit, failed authentication
public String getSignatureStatus() {
return signatureStatus;
}

public void setSignatureStatus(String signatureStatus) {
this.signatureStatus = signatureStatus;
}











// new field for rate-limit, failed authentication
@Expose
@Column(name = "failed_attempt", insertable = false)
private Integer failedAttempt;
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/iemr/admin/data/user/M_User.java
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public void setEmergencyContactNo(String emergencyContactNo) {
EmergencyContactNo = emergencyContactNo;
}

public boolean isIsSupervisor() {
public Boolean isIsSupervisor() {
return IsSupervisor;
}

Expand All @@ -272,7 +272,7 @@ public void setIsSupervisor(boolean isSupervisor) {
}

public boolean isDeleted() {
return Deleted;
return Boolean.TRUE.equals(Deleted);
}

public void setDeleted(boolean deleted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonParser;
import com.iemr.admin.data.blocking.M_Providerservicemapping_Blocking;
import com.iemr.admin.data.employeemaster.EmployeeSignature;
import com.iemr.admin.data.employeemaster.M_Community;
import com.iemr.admin.data.employeemaster.M_Gender;
import com.iemr.admin.data.employeemaster.M_ProviderServiceMap1;
Expand All @@ -80,6 +81,7 @@
import com.iemr.admin.repo.blocking.MProviderservicemappingBlockingRepo;
import com.iemr.admin.repo.employeemaster.EmployeeMasterRepo;
import com.iemr.admin.repo.employeemaster.EmployeeMasterRepoo;
import com.iemr.admin.repo.employeemaster.EmployeeSignatureRepo;
import com.iemr.admin.repo.employeemaster.M_CommunityRepo;
import com.iemr.admin.repo.employeemaster.M_GenderRepo;
import com.iemr.admin.repo.employeemaster.M_ProviderServiceMap1Repo;
Expand All @@ -104,7 +106,6 @@
import com.iemr.admin.utils.mapper.InputMapper;
import com.iemr.admin.utils.response.OutputResponse;

import jakarta.servlet.http.HttpServletRequest;

@Service
public class EmployeeMasterServiceImpl implements EmployeeMasterInter {
Expand Down Expand Up @@ -190,6 +191,9 @@ public void setConfigProperties(ConfigProperties configProperties) {

@Autowired
M_ServiceMasterRepo serviceMasterRepo;

@Autowired
private EmployeeSignatureRepo employeeSignatureRepo;
/*
* @Override public ArrayList<M_Role> getAllRole() { //ArrayList<M_Role> resSet
* = new ArrayList<M_Role>(); //resSet =
Expand Down Expand Up @@ -1089,6 +1093,19 @@ public ArrayList<V_Showuser> getEmployeeDetails4(Integer serviceProviderID) {
public ArrayList<M_User1> getEmployeeByDesiganationID(Integer designationID, Integer serviceProviderID) {
ArrayList<M_User1> getEmpByDesiganation = employeeMasterRepoo.getempByDesiganation(designationID,
serviceProviderID);
for (M_User1 user : getEmpByDesiganation) {
Integer userID = user.getUserID();
EmployeeSignature signature = employeeSignatureRepo.findOneByUserID(Long.valueOf(userID));
if (null != signature) {
if (signature.getDeleted()) {
user.setSignatureStatus("InActive");
} else {
user.setSignatureStatus("Active");
}
} else {
user.setSignatureStatus(null);
}
}
return getEmpByDesiganation;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ public interface EmployeeSignatureService {

Long uploadSignature(EmployeeSignature empSign);

EmployeeSignature updateUserSignatureStatus(String activateUser);

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/
package com.iemr.admin.service.employeemaster;

import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -67,4 +68,19 @@ public Boolean existSignature(Long userID) {
return employeeSignatureRepo.countByUserIDAndSignatureNotNull(userID)>0;
}

@Override
public EmployeeSignature updateUserSignatureStatus(String activateUser) {
JSONObject obj = new JSONObject(activateUser);
Long userID = obj.getLong("userID");
//String role = obj.getString("role");
boolean active = obj.getBoolean("active");
EmployeeSignature signature = employeeSignatureRepo.findOneByUserID(userID);
if (active) {
signature.setDeleted(false);
} else {
signature.setDeleted(true);
}
return employeeSignatureRepo.save(signature);
}
Comment on lines +71 to +83
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

NPE when signature record is absent; add not-found handling

findOneByUserID may return null; subsequent setDeleted will NPE. Validate existence and fail fast (or upsert), and simplify flag toggle.

Apply this diff:

-    EmployeeSignature signature = employeeSignatureRepo.findOneByUserID(userID);
-    if (active) {
-        signature.setDeleted(false);
-    } else {
-        signature.setDeleted(true);
-    }
-    return employeeSignatureRepo.save(signature);
+    EmployeeSignature signature = employeeSignatureRepo.findOneByUserID(userID);
+    if (signature == null) {
+        throw new IllegalArgumentException("No signature found for userID: " + userID);
+    }
+    signature.setDeleted(!active);
+    return employeeSignatureRepo.save(signature);

Optional: accept a DTO and validate schema; consider auditing modifiedBy.

πŸ“ 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
@Override
public EmployeeSignature updateUserSignatureStatus(String activateUser) {
JSONObject obj = new JSONObject(activateUser);
Long userID = obj.getLong("userID");
//String role = obj.getString("role");
boolean active = obj.getBoolean("active");
EmployeeSignature signature = employeeSignatureRepo.findOneByUserID(userID);
if (active) {
signature.setDeleted(false);
} else {
signature.setDeleted(true);
}
return employeeSignatureRepo.save(signature);
}
@Override
public EmployeeSignature updateUserSignatureStatus(String activateUser) {
JSONObject obj = new JSONObject(activateUser);
Long userID = obj.getLong("userID");
//String role = obj.getString("role");
boolean active = obj.getBoolean("active");
EmployeeSignature signature = employeeSignatureRepo.findOneByUserID(userID);
if (signature == null) {
throw new IllegalArgumentException("No signature found for userID: " + userID);
}
signature.setDeleted(!active);
return employeeSignatureRepo.save(signature);
}
πŸ€– Prompt for AI Agents
In
src/main/java/com/iemr/admin/service/employeemaster/EmployeeSignatureServiceImpl.java
around lines 71 to 84, the code assumes
employeeSignatureRepo.findOneByUserID(userID) always returns a non-null
EmployeeSignature which causes an NPE when no record exists; modify the method
to check the repository result before using it and handle the not-found case
(either throw a clear exception like EntityNotFoundException with a helpful
message or create a new EmployeeSignature for an upsert), replace the redundant
if/else with a single setDeleted(!active) call, and then save and return the
entity; also validate the incoming JSON fields (userID and active) before use
and consider setting modifiedBy/modifiedAt if audit fields exist.


}