diff --git a/src/main/java/io/shiftleft/controller/CustomerController.java b/src/main/java/io/shiftleft/controller/CustomerController.java index 40e1c49..51e1812 100644 --- a/src/main/java/io/shiftleft/controller/CustomerController.java +++ b/src/main/java/io/shiftleft/controller/CustomerController.java @@ -277,34 +277,113 @@ public void saveSettings(HttpServletResponse httpResponse, WebRequest request) t * @return String * @throws IOException */ - @RequestMapping(value = "/debug", method = RequestMethod.GET) - public String debug(@RequestParam String customerId, - @RequestParam int clientId, - @RequestParam String firstName, - @RequestParam String lastName, - @RequestParam String dateOfBirth, - @RequestParam String ssn, - @RequestParam String socialSecurityNum, - @RequestParam String tin, - @RequestParam String phoneNumber, - HttpServletResponse httpResponse, - WebRequest request) throws IOException{ - - // empty for now, because we debug - Set accounts1 = new HashSet(); - //dateofbirth example -> "1982-01-10" - Customer customer1 = new Customer(customerId, clientId, firstName, lastName, DateTime.parse(dateOfBirth).toDate(), - ssn, socialSecurityNum, tin, phoneNumber, new Address("Debug str", - "", "Debug city", "CA", "12345"), - accounts1); - - customerRepository.save(customer1); - httpResponse.setStatus(HttpStatus.CREATED.value()); - httpResponse.setHeader("Location", String.format("%s/customers/%s", - request.getContextPath(), customer1.getId())); - - return customer1.toString().toLowerCase().replace("script",""); - } +@RequestMapping(value = "/debug", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) +public String debug(@RequestParam String customerId, + @RequestParam int clientId, + @RequestParam String firstName, + @RequestParam String lastName, + @RequestParam String dateOfBirth, + @RequestParam String ssn, + @RequestParam String socialSecurityNum, + @RequestParam String tin, + @RequestParam String phoneNumber, + HttpServletResponse httpResponse, + WebRequest request) throws IOException { + + // Input validation for customerId + if (customerId == null || customerId.trim().isEmpty()) { + httpResponse.setStatus(HttpStatus.BAD_REQUEST.value()); + return "Invalid customerId"; + } + + // Input validation for names + if (firstName == null || firstName.trim().isEmpty() || + lastName == null || lastName.trim().isEmpty()) { + httpResponse.setStatus(HttpStatus.BAD_REQUEST.value()); + return "Invalid name parameters"; + } + + // Validate date format before parsing + if (dateOfBirth == null || !dateOfBirth.matches("\\d{4}-\\d{2}-\\d{2}")) { + httpResponse.setStatus(HttpStatus.BAD_REQUEST.value()); + return "Invalid date format. Expected: YYYY-MM-DD"; + } + + try { + // Empty account set for debug purposes + Set accounts1 = new HashSet(); + + // Parse date with error handling + Customer customer1 = new Customer( + customerId, + clientId, + firstName, + lastName, + DateTime.parse(dateOfBirth).toDate(), + ssn, + socialSecurityNum, + tin, + phoneNumber, + new Address("Debug str", "", "Debug city", "CA", "12345"), + accounts1 + ); + + customerRepository.save(customer1); + httpResponse.setStatus(HttpStatus.CREATED.value()); + httpResponse.setHeader("Location", String.format("%s/customers/%s", + request.getContextPath(), customer1.getId())); + + // Sanitize output by HTML-encoding all user-controlled data + // This prevents XSS by converting special HTML characters to entities + String sanitizedOutput = StringEscapeUtils.escapeHtml4(customer1.toString()); + + // Set content type to plain text to prevent browser interpretation as HTML + httpResponse.setContentType(MediaType.TEXT_PLAIN_VALUE); + + return sanitizedOutput; + + } catch (IllegalArgumentException e) { + // Handle date parsing errors + httpResponse.setStatus(HttpStatus.BAD_REQUEST.value()); + return "Invalid date format"; + } catch (Exception e) { + // Handle other errors securely without exposing sensitive information + httpResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + return "An error occurred while processing the request"; + } +} + + + // Save customer to repository + customerRepository.save(customer1); + + // Set response status and location header + httpResponse.setStatus(HttpStatus.CREATED.value()); + httpResponse.setHeader("Location", String.format("%s/customers/%s", + request.getContextPath(), customer1.getId())); + + // Return sanitized JSON response instead of toString() to avoid XSS + // Set content type to application/json to prevent HTML interpretation + httpResponse.setContentType("application/json"); + + // Create a safe JSON response with escaped values + String safeResponse = String.format( + "{\"status\":\"created\",\"customerId\":\"%s\",\"clientId\":%d,\"message\":\"Customer created successfully\"}", + StringEscapeUtils.escapeJson(customerId), + clientId + ); + + return safeResponse; + + } catch (IllegalArgumentException e) { + // Handle date parsing errors + logger.error("Invalid date format provided: {}", dateOfBirth); + httpResponse.setStatus(HttpStatus.BAD_REQUEST.value()); + httpResponse.setContentType("application/json"); + return "{\"error\":\"Invalid date format. Expected format: YYYY-MM-DD\"}"; + } +} + /** * Debug test for saving and reading a customer diff --git a/src/main/java/io/shiftleft/model/Customer.java b/src/main/java/io/shiftleft/model/Customer.java index 6ecdc30..0dd9eb8 100644 --- a/src/main/java/io/shiftleft/model/Customer.java +++ b/src/main/java/io/shiftleft/model/Customer.java @@ -16,9 +16,11 @@ public class Customer { public Customer() { } - public Customer(String customerId, int clientId, String firstName, String lastName, Date dateOfBirth, String ssn, +public Customer(String customerId, int clientId, String firstName, String lastName, Date dateOfBirth, String ssn, String socialInsurancenum, String tin, String phoneNumber, Address address, Set accounts) { super(); + // Store data as-is in the model + // Sanitization should occur at the presentation layer when displaying to users this.clientId = clientId; this.customerId = customerId; this.firstName = firstName; @@ -30,7 +32,18 @@ public Customer(String customerId, int clientId, String firstName, String lastNa this.phoneNumber = phoneNumber; this.address = address; this.accounts = accounts; - } +} + + +// Helper method to sanitize inputs +private String sanitizeInput(String input) { + if (input == null) { + return null; + } + // Remove any HTML/script tags and escape special characters + return StringEscapeUtils.escapeHtml4(input.trim()); +} + @Id @GeneratedValue(strategy = GenerationType.AUTO) @@ -156,12 +169,26 @@ public void setAccounts(Set accounts) { this.accounts = accounts; } - @Override - public String toString() { +@Override +public String toString() { + // Note: This toString method is used for debugging purposes + // When returning this to a web response, it MUST be HTML-encoded + // The encoding is handled at the controller layer (CustomerController.debug) return "Customer [id=" + id + ", customerId=" + customerId + ", clientId=" + clientId + ", firstName=" + firstName + ", lastName=" + lastName + ", dateOfBirth=" + dateOfBirth + ", ssn=" + ssn + ", socialInsurancenum=" + socialInsurancenum + ", tin=" + tin + ", phoneNumber=" + phoneNumber + ", address=" + address + ", accounts=" + accounts + "]"; - } +} + + +// Helper method to mask sensitive data in logs and output +private String maskSensitiveData(String data) { + if (data == null || data.length() < 4) { + return "****"; + } + // Show only last 4 characters for sensitive data + return "****" + StringEscapeUtils.escapeHtml4(data.substring(data.length() - 4)); +} + }