From d7cce50e038355f6de88115e23810d44ccdbe60e Mon Sep 17 00:00:00 2001
From: agitrubard
Date: Thu, 2 Jan 2025 18:26:48 +0300
Subject: [PATCH 1/5] AYS-509 | `isBlank()` Method Has Been Removed from
`AysPhoneNumberRequest`
---
.../ays/common/model/request/AysPhoneNumberRequest.java | 5 -----
.../request/EmergencyEvacuationApplicationRequest.java | 9 ++++++---
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/main/java/org/ays/common/model/request/AysPhoneNumberRequest.java b/src/main/java/org/ays/common/model/request/AysPhoneNumberRequest.java
index 2e0a5ea08..74d993d40 100644
--- a/src/main/java/org/ays/common/model/request/AysPhoneNumberRequest.java
+++ b/src/main/java/org/ays/common/model/request/AysPhoneNumberRequest.java
@@ -3,7 +3,6 @@
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
-import org.apache.commons.lang3.StringUtils;
import org.ays.common.util.validation.PhoneNumber;
/**
@@ -35,8 +34,4 @@ public String toString() {
.formatted(this.countryCode, this.lineNumber);
}
- public boolean isBlank() {
- return StringUtils.isBlank(this.countryCode) && StringUtils.isBlank(this.lineNumber);
- }
-
}
diff --git a/src/main/java/org/ays/emergency_application/model/request/EmergencyEvacuationApplicationRequest.java b/src/main/java/org/ays/emergency_application/model/request/EmergencyEvacuationApplicationRequest.java
index 35471d1c8..7545a7a0a 100644
--- a/src/main/java/org/ays/emergency_application/model/request/EmergencyEvacuationApplicationRequest.java
+++ b/src/main/java/org/ays/emergency_application/model/request/EmergencyEvacuationApplicationRequest.java
@@ -86,9 +86,12 @@ private boolean isAllApplicantFieldsFilled() {
return true;
}
+
return !StringUtils.isBlank(this.applicantFirstName) && !StringUtils.isBlank(this.applicantLastName)
&&
- this.applicantPhoneNumber != null && !this.applicantPhoneNumber.isBlank();
+ this.applicantPhoneNumber != null
+ &&
+ !(StringUtils.isBlank(this.applicantPhoneNumber.getCountryCode()) && StringUtils.isBlank(this.applicantPhoneNumber.getLineNumber()));
}
@JsonIgnore
@@ -111,7 +114,7 @@ private boolean isPhoneNumberMustNotBeSameOne() {
@AssertTrue(message = "source city/district and target city/district must be different")
@SuppressWarnings("This method is unused by the application directly but Spring is using it in the background.")
private boolean isSourceCityAndDistrictDifferentFromTargetCityAndDistrict() {
-
+
if (this.sourceCity == null || this.sourceDistrict == null || this.targetCity == null || this.targetDistrict == null) {
return true;
}
@@ -119,7 +122,7 @@ private boolean isSourceCityAndDistrictDifferentFromTargetCityAndDistrict() {
if (!this.sourceCity.equalsIgnoreCase(this.targetCity)){
return true;
}
-
+
return !this.sourceDistrict.equalsIgnoreCase(this.targetDistrict);
}
From 05592922bfbae0aa1441e22c7bd252bfa7e72221 Mon Sep 17 00:00:00 2001
From: agitrubard
Date: Thu, 2 Jan 2025 19:32:21 +0300
Subject: [PATCH 2/5] AYS-509 | `AysMaskUtil` Has Been Created for Masking
Values
---
.../java/org/ays/common/util/AysMaskUtil.java | 181 ++++++++++++++++++
1 file changed, 181 insertions(+)
create mode 100644 src/main/java/org/ays/common/util/AysMaskUtil.java
diff --git a/src/main/java/org/ays/common/util/AysMaskUtil.java b/src/main/java/org/ays/common/util/AysMaskUtil.java
new file mode 100644
index 000000000..0d1c9b5d4
--- /dev/null
+++ b/src/main/java/org/ays/common/util/AysMaskUtil.java
@@ -0,0 +1,181 @@
+package org.ays.common.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.experimental.UtilityClass;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Iterator;
+
+/**
+ * Utility class for masking sensitive information in JSON data.
+ *
+ * This class provides methods for masking fields within a {@link JsonNode} or individual string values.
+ * It is designed to handle common sensitive fields such as tokens, passwords, email addresses, and more.
+ *
+ *
+ * Unmasked JSON:
+ *
+ * {
+ * "emailAddress": "test@example.com",
+ * "password": "123456789",
+ * "lineNumber": "1234567890",
+ * "address": "123 Main Street, Springfield",
+ * "firstName": "John",
+ * "lastName": "Doe"
+ * }
+ *
+ *
+ * Masked JSON:
+ *
+ * {
+ * "emailAddress": "tes******com",
+ * "password": "******",
+ * "lineNumber": "******7890",
+ * "address": "123 Main ******field",
+ * "firstName": "Joh******ohn",
+ * "lastName": "Doe******oe"
+ * }
+ *
+ */
+@UtilityClass
+public class AysMaskUtil {
+
+ private static final String MASKED_VALUE = "******";
+
+ /**
+ * Masks sensitive fields in the given {@link JsonNode}.
+ *
+ * This method recursively iterates through the fields in the JSON object or array,
+ * and applies masking to fields identified as sensitive.
+ *
+ *
+ * @param jsonNode the JSON node to process for masking
+ */
+ public static void mask(final JsonNode jsonNode) {
+
+ if (jsonNode.isObject()) {
+
+ ObjectNode objectNode = (ObjectNode) jsonNode;
+ Iterator fieldNames = objectNode.fieldNames();
+
+ while (fieldNames.hasNext()) {
+
+ String fieldName = fieldNames.next();
+ JsonNode fieldValue = objectNode.get(fieldName);
+
+ if (fieldValue.isValueNode()) {
+ String maskedValue = mask(fieldName, fieldValue.asText());
+ objectNode.put(fieldName, maskedValue);
+ continue;
+ }
+
+ mask(fieldValue);
+ }
+
+ return;
+ }
+
+ if (jsonNode.isArray()) {
+ ArrayNode arrayNode = (ArrayNode) jsonNode;
+ for (JsonNode arrayElement : arrayNode) {
+ mask(arrayElement);
+ }
+ }
+ }
+
+ /**
+ * Masks the value of a specific field based on its name.
+ *
+ * This method identifies sensitive fields by their names and applies the appropriate
+ * masking strategy. Fields not recognized as sensitive are returned without modification.
+ *
+ *
+ * @param field the name of the field
+ * @param value the value to mask
+ * @return the masked value
+ */
+ public static String mask(final String field, final String value) {
+
+ if ("null".equalsIgnoreCase(value) || StringUtils.isBlank(value)) {
+ return value;
+ }
+
+ return switch (field) {
+ case "Authorization", "accessToken", "refreshToken" -> maskToken(value);
+ case "password" -> maskPassword();
+ case "emailAddress" -> maskEmailAddress(value);
+ case "lineNumber" -> maskLineNumber(value);
+ case "address" -> maskAddress(value);
+ case "firstName", "lastName", "applicantFirstName", "applicantLastName" -> maskName(value);
+ default -> value;
+ };
+ }
+
+ /**
+ * Masks token fields such as "Authorization", "accessToken", or "refreshToken".
+ *
+ * @param value the token value to mask
+ * @return the masked token
+ */
+ private static String maskToken(String value) {
+ return value.substring(0, 20) + MASKED_VALUE;
+ }
+
+ /**
+ * Masks password fields, replacing their values with a fixed placeholder.
+ *
+ * @return the masked password placeholder
+ */
+ private static String maskPassword() {
+ return MASKED_VALUE;
+ }
+
+ /**
+ * Masks email addresses by revealing the first three and last three characters,
+ * replacing the rest with asterisks.
+ *
+ * @param value the email address to mask
+ * @return the masked email address
+ */
+ private static String maskEmailAddress(String value) {
+ int length = value.length();
+ String firstThree = value.substring(0, 3);
+ String lastThree = value.substring(length - 3);
+ return firstThree + MASKED_VALUE + lastThree;
+ }
+
+ /**
+ * Masks address fields by revealing the first ten and last ten characters,
+ * replacing the middle part with asterisks.
+ *
+ * @param value the address to mask
+ * @return the masked address
+ */
+ private static String maskAddress(String value) {
+ return value.substring(0, 10) + MASKED_VALUE + value.substring(value.length() - 10);
+ }
+
+ /**
+ * Masks line numbers by revealing the last four digits, replacing the preceding digits with asterisks.
+ *
+ * @param value the line number to mask
+ * @return the masked line number
+ */
+ private static String maskLineNumber(String value) {
+ return MASKED_VALUE + value.substring(value.length() - 4);
+ }
+
+ /**
+ * Masks name fields such as "firstName", "lastName", or similar by revealing the first three
+ * and last three characters, replacing the middle part with asterisks.
+ *
+ * @param value the name to mask
+ * @return the masked name
+ */
+ private static String maskName(String value) {
+ return value.substring(0, 3) + MASKED_VALUE + value.substring(value.length() - 3);
+ }
+
+}
From 7f96fb4361901836b299e19e337d589b48a10df0 Mon Sep 17 00:00:00 2001
From: agitrubard
Date: Thu, 2 Jan 2025 19:34:41 +0300
Subject: [PATCH 3/5] AYS-509 | `toEscapedJson` Method Has Been Changed as
`toMaskedEscapedJson`
---
.../org/ays/common/model/AysAuditLog.java | 4 ++--
.../java/org/ays/common/util/AysJsonUtil.java | 20 ++++++++++++-------
2 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/main/java/org/ays/common/model/AysAuditLog.java b/src/main/java/org/ays/common/model/AysAuditLog.java
index c586e2772..8696e4859 100644
--- a/src/main/java/org/ays/common/model/AysAuditLog.java
+++ b/src/main/java/org/ays/common/model/AysAuditLog.java
@@ -84,7 +84,7 @@ public AysAuditLogBuilder aysHttpServletRequest(final AysHttpServletRequest aysH
this.request.httpMethod = aysHttpServletRequest.getMethod();
this.request.path = aysHttpServletRequest.getPath();
- this.request.body = AysJsonUtil.toEscapedJson(aysHttpServletRequest.getBody());
+ this.request.body = AysJsonUtil.toMaskedEscapedJson(aysHttpServletRequest.getBody());
return this;
}
@@ -99,7 +99,7 @@ public AysAuditLogBuilder aysHttpHeader(final AysHttpHeader aysHttpHeader) {
public AysAuditLogBuilder aysHttpServletResponse(final AysHttpServletResponse aysHttpServletResponse) {
this.response.httpStatusCode = aysHttpServletResponse.getStatus();
- this.response.body = AysJsonUtil.toEscapedJson(aysHttpServletResponse.getBody());
+ this.response.body = AysJsonUtil.toMaskedEscapedJson(aysHttpServletResponse.getBody());
return this;
}
diff --git a/src/main/java/org/ays/common/util/AysJsonUtil.java b/src/main/java/org/ays/common/util/AysJsonUtil.java
index 8b6b4d8b6..2b61aab67 100644
--- a/src/main/java/org/ays/common/util/AysJsonUtil.java
+++ b/src/main/java/org/ays/common/util/AysJsonUtil.java
@@ -33,18 +33,24 @@ public static String toJson(final Object object) {
}
/**
- * Converts a formatted JSON string into an unformatted (compact) JSON string.
+ * Converts a JSON string into a masked and escaped JSON string.
*
- * This method parses the input JSON string into a tree structure using the {@link ObjectMapper}
- * and then converts it back to a compact string representation.
- * If an exception occurs during parsing, an empty string is returned.
+ * This method performs the following steps:
+ *
+ * - Parses the input JSON string into a tree structure using the {@link ObjectMapper}.
+ * - Masks sensitive fields in the JSON using {@link AysMaskUtil}.
+ * - Escapes double quotes in the JSON string by replacing them with backslash-escaped quotes.
+ *
+ * If an exception occurs during parsing or masking, an empty string is returned.
+ *
*
- * @param json the formatted JSON string to be converted
- * @return a compact (unformatted) JSON string, or an empty string if the input is invalid
+ * @param json The JSON string to be masked and escaped.
+ * @return A masked and escaped JSON string, or an empty string if the input is invalid.
*/
- public static String toEscapedJson(final String json) {
+ public static String toMaskedEscapedJson(final String json) {
try {
final JsonNode jsonNode = OBJECT_MAPPER.readTree(json);
+ AysMaskUtil.mask(jsonNode);
return jsonNode.toString().replace("\"", "\\\"");
} catch (JsonProcessingException exception) {
return "";
From 6cf31f47a76fcde8d09ff7b07de8dcc87dbc5ec9 Mon Sep 17 00:00:00 2001
From: agitrubard
Date: Thu, 2 Jan 2025 19:34:59 +0300
Subject: [PATCH 4/5] AYS-509 | `Authorization` Header Has Been Masked
---
.../org/ays/common/model/request/AysHttpHeader.java | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/ays/common/model/request/AysHttpHeader.java b/src/main/java/org/ays/common/model/request/AysHttpHeader.java
index cfa1b1456..0baa27c75 100644
--- a/src/main/java/org/ays/common/model/request/AysHttpHeader.java
+++ b/src/main/java/org/ays/common/model/request/AysHttpHeader.java
@@ -3,6 +3,7 @@
import jakarta.servlet.http.HttpServletRequest;
import lombok.EqualsAndHashCode;
import lombok.Getter;
+import org.ays.common.util.AysMaskUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
@@ -58,7 +59,15 @@ private String getAllHeaders(final HttpServletRequest httpServletRequest) {
headerNames.remove(X_FORWARDED_FOR.toLowerCase());
return headerNames
.stream()
- .map(headerName -> headerName + ":" + httpServletRequest.getHeader(headerName))
+ .map(headerName -> {
+
+ final String headerValue = httpServletRequest.getHeader(headerName);
+ if (headerName.equalsIgnoreCase(AUTHORIZATION)) {
+ return headerName + ":" + AysMaskUtil.mask(headerName, headerValue);
+ }
+
+ return headerName + ":" + headerValue;
+ })
.collect(Collectors.joining("; "));
}
From 26245d3572b1a6c3069d734717c7a526a61b2b60 Mon Sep 17 00:00:00 2001
From: agitrubard
Date: Thu, 2 Jan 2025 19:56:04 +0300
Subject: [PATCH 5/5] AYS-509 | Length Check Has Been Added to All Masking
Methods
---
.../java/org/ays/common/util/AysMaskUtil.java | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/main/java/org/ays/common/util/AysMaskUtil.java b/src/main/java/org/ays/common/util/AysMaskUtil.java
index 0d1c9b5d4..d153a8974 100644
--- a/src/main/java/org/ays/common/util/AysMaskUtil.java
+++ b/src/main/java/org/ays/common/util/AysMaskUtil.java
@@ -120,6 +120,11 @@ public static String mask(final String field, final String value) {
* @return the masked token
*/
private static String maskToken(String value) {
+
+ if (value.length() <= 20) {
+ return value;
+ }
+
return value.substring(0, 20) + MASKED_VALUE;
}
@@ -140,6 +145,11 @@ private static String maskPassword() {
* @return the masked email address
*/
private static String maskEmailAddress(String value) {
+
+ if (value.length() <= 3) {
+ return value;
+ }
+
int length = value.length();
String firstThree = value.substring(0, 3);
String lastThree = value.substring(length - 3);
@@ -154,6 +164,11 @@ private static String maskEmailAddress(String value) {
* @return the masked address
*/
private static String maskAddress(String value) {
+
+ if (value.length() <= 10) {
+ return value;
+ }
+
return value.substring(0, 10) + MASKED_VALUE + value.substring(value.length() - 10);
}
@@ -164,6 +179,11 @@ private static String maskAddress(String value) {
* @return the masked line number
*/
private static String maskLineNumber(String value) {
+
+ if (value.length() <= 4) {
+ return value;
+ }
+
return MASKED_VALUE + value.substring(value.length() - 4);
}
@@ -175,6 +195,11 @@ private static String maskLineNumber(String value) {
* @return the masked name
*/
private static String maskName(String value) {
+
+ if (value.length() <= 3) {
+ return value;
+ }
+
return value.substring(0, 3) + MASKED_VALUE + value.substring(value.length() - 3);
}