Skip to content

Conversation

@arvi18
Copy link

@arvi18 arvi18 commented Aug 11, 2025

New Features

Introduced a value object for notification recipients supporting multiple channels and enhanced API documentation.
Added automatic desensitization (masking) of phone numbers and emails in notification recipient data for improved privacy.
Implemented caching to restore original contact information when editing or testing notification recipients.
Bug Fixes

Ensured original contact details are correctly restored from cache during recipient edits and test message sends.
Tests

Added tests to verify phone and email desensitization and caching behavior.
Chores

Added Hutool utility library as a new project dependency.

Summary by CodeRabbit

  • New Features
    • Phone and email are now masked in API responses to protect privacy.
    • Editing and test sending seamlessly accept masked contact values.
  • Refactor
    • Receiver list API now returns a dedicated view object instead of the domain entity, aligning the payload with client-facing needs.
  • Tests
    • Added unit tests to validate phone and email desensitization behavior.
  • Chores
    • Added dependencies for Excel/OOXML handling and utility functions.

@coderabbitai
Copy link

coderabbitai bot commented Aug 11, 2025

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between b02837b and 8ade3d3.

📒 Files selected for processing (10)
  • hertzbeat-common/pom.xml (1 hunks)
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java (2 hunks)
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java (1 hunks)
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java (1 hunks)
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java (1 hunks)
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java (1 hunks)
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java (1 hunks)
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java (2 hunks)
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (3 hunks)
  • pom.xml (2 hunks)
 _________________________________________________
< I am the right brain to your SAST's left brain. >
 -------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

Tip

CodeRabbit can use Shopify Theme Check to improve the quality of Shopify theme reviews.

Add a configuration file to your project to customize how CodeRabbit runs Shopify Theme Check.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch clone-e-branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @arvi18, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

I've implemented a new feature to enhance privacy by desensitizing sensitive information, specifically phone numbers and email addresses, for notification recipients. This change introduces automatic masking of these details when displayed, while also incorporating a caching mechanism to temporarily store the original values. This caching allows the system to correctly restore the unmasked contact information when users need to edit recipient details or send test messages, ensuring data integrity during these operations. Additionally, I've introduced a new value object for notification recipients to support multiple communication channels and integrated the Hutool utility library to assist with data manipulation.

Highlights

  • Sensitive Data Desensitization: Implemented automatic masking of phone numbers and email addresses for notification recipients to improve privacy and data security.
  • Original Data Caching: Introduced a caching mechanism to temporarily store the original, unmasked contact information (phone and email) when recipients are viewed, allowing for restoration.
  • Data Restoration for Operations: Ensured that the original contact details are correctly restored from the cache when editing notification recipients or sending test messages, preventing operations on masked data.
  • New Notification Recipient Value Object: Created a new value object, NoticeReceiverVO, to support multiple notification channels and integrate the new desensitization features.
  • New Project Dependency: Added the Hutool utility library as a new project dependency, providing various utility functions used in the desensitization process.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@refacto-test
Copy link

refacto-test bot commented Aug 11, 2025

🤖 Refacto.AI: PR Summary & Reviewer Guide

🎯 1. The Goal (Why was this PR raised?)

This PR aims to implement data desensitization for sensitive information such as email addresses and phone numbers in the HertzBeat application. The goal is to protect user privacy by masking sensitive data when it's displayed in the application interface.

🗺️ 2. The Changes (What was implemented?)

File Name Implementation Details Goal / Requirement Addressed
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java (Added) Created a new value object class for notice receivers with desensitization annotations for email and phone fields. Implement data desensitization
pom.xml (Modified) Added Hutool dependency with version 5.8.20 to the project. Add utility library for desensitization
hertzbeat-common/pom.xml (Modified) Added Hutool dependency to the common module. Add utility library for desensitization
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java (Added) Created a custom JSON serializer that masks email addresses by replacing characters between the first character and @ with asterisks. Implement email desensitization
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java (Added) Created a custom JSON serializer that masks phone numbers using Hutool's desensitization utility. Implement phone desensitization
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java (Added) Added unit tests for the email desensitization serializer. Test email desensitization
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java (Added) Added unit tests for the phone desensitization serializer. Test phone desensitization
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java (Modified) Modified the getReceivers endpoint to return NoticeReceiverVO objects with desensitized data instead of NoticeReceiver entities. Apply desensitization in API responses
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (Modified) Added logic to handle desensitized data when editing receivers or sending test messages by retrieving original values from cache. Handle desensitized data in service layer
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java (Modified) Added a new cache for storing mappings between desensitized and original values. Store desensitization mappings

🤔 3. Key Areas for Human Review

Area of Concern: Desensitization Logic Implementation

  • File: hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java and PhoneDesensitizationSerializer.java
  • Why: These files implement the core desensitization logic for sensitive data. Any issues here could lead to either over-exposure of sensitive data or incorrect masking.
  • Testing Instruction: Test the API endpoints that return user data containing emails and phone numbers to verify that the data is properly masked. For example, an email like "[email protected]" should appear as "u****@example.com".

Area of Concern: Handling of Original Values in Edit Operations

  • File: hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (Lines 136-142 and 279-287)
  • Why: The code retrieves original values from cache when editing receivers or sending test messages. If this logic fails, users might not be able to edit records with desensitized data.
  • Testing Instruction: Try editing a notice receiver that has desensitized email/phone fields and verify that the system correctly processes the edit without losing the original values.

Area of Concern: Cache Implementation for Desensitization Mappings

  • File: hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java
  • Why: The PR introduces a new cache for storing mappings between desensitized and original values. If this cache doesn't function properly, the system might lose the ability to recover original values.
  • Testing Instruction: Test scenarios where desensitized data needs to be converted back to original values, particularly after the application has been running for some time, to ensure the cache is working correctly and not expiring prematurely.

Area of Concern: API Response Transformation

  • File: hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java (Line 90)
  • Why: The controller now returns a different type (NoticeReceiverVO instead of NoticeReceiver) with desensitized data. This could potentially break clients expecting the original format.
  • Testing Instruction: Verify that all client applications or UI components that consume the getReceivers endpoint still function correctly with the new response format containing desensitized data.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a feature to desensitize sensitive information like phone numbers and emails in notification recipients. The implementation uses custom Jackson serializers and a caching mechanism to store and restore original values. The changes are generally well-structured, but there are several areas for improvement regarding dependency management, code style, test correctness, and code duplication. I've provided specific comments with suggestions to address these points.

Comment on lines +24 to +41
public void test() throws JsonProcessingException {
NoticeReceiverVO noticeReceiver = new NoticeReceiverVO();
noticeReceiver.setId(1L);
noticeReceiver.setEmail("[email protected]");

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(String.class,emailDesensitizationSerializer);

objectMapper.registerModule(simpleModule);

String jsonString = objectMapper.writeValueAsString(noticeReceiver);
NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);

CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();

assertEquals(noticeReceiver.getEmail(),desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getEmail()));
}

Choose a reason for hiding this comment

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

high

The test setup is incorrect. By registering the serializer for String.class (simpleModule.addSerializer(String.class,emailDesensitizationSerializer)), you are applying it to all string properties of NoticeReceiverVO, not just the email field as intended by the @JsonSerialize annotation. This can lead to unexpected side effects and does not accurately test the production behavior.

The test should rely on the @JsonSerialize annotation on the NoticeReceiverVO.email field to be picked up by Jackson. Also, it's good practice to assert that the serialized JSON contains the desensitized value.

Please refactor the test to correctly reflect its usage.

    public void test() throws JsonProcessingException {
        NoticeReceiverVO noticeReceiver = new NoticeReceiverVO();
        noticeReceiver.setId(1L);
        noticeReceiver.setEmail("[email protected]");

        ObjectMapper objectMapper = new ObjectMapper();

        String jsonString = objectMapper.writeValueAsString(noticeReceiver);

        // Assert that the email in the JSON string is desensitized
        String desensitizedEmail = "1****@163.com";
        assertTrue(jsonString.contains("\"email\":\"" + desensitizedEmail + "\""));

        NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);
        assertEquals(desensitizedEmail, noticeReceiverVO.getEmail());

        CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
        assertEquals("[email protected]", desensitizationMapCache.get(noticeReceiverVO.getId() + "_" + noticeReceiverVO.getEmail()));
    }

Comment on lines +24 to +41
public void test() throws JsonProcessingException {
NoticeReceiverVO noticeReceiver = new NoticeReceiverVO();
noticeReceiver.setId(1L);
noticeReceiver.setPhone("12345678910");

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(String.class,phoneDesensitizationSerializer);

objectMapper.registerModule(simpleModule);

String jsonString = objectMapper.writeValueAsString(noticeReceiver);
NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);

CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();

assertEquals(noticeReceiver.getPhone(),desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getPhone()));
}

Choose a reason for hiding this comment

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

high

The test setup is incorrect. By registering the serializer for String.class (simpleModule.addSerializer(String.class,phoneDesensitizationSerializer)), you are applying it to all string properties of NoticeReceiverVO, not just the phone field as intended by the @JsonSerialize annotation. This can lead to unexpected side effects and does not accurately test the production behavior.

The test should rely on the @JsonSerialize annotation on the NoticeReceiverVO.phone field to be picked up by Jackson. Also, it's good practice to assert that the serialized JSON contains the desensitized value.

Please refactor the test to correctly reflect its usage.

    public void test() throws JsonProcessingException {
        NoticeReceiverVO noticeReceiver = new NoticeReceiverVO();
        noticeReceiver.setId(1L);
        noticeReceiver.setPhone("12345678910");

        ObjectMapper objectMapper = new ObjectMapper();

        String jsonString = objectMapper.writeValueAsString(noticeReceiver);

        // Assert that the phone in the JSON string is desensitized
        String desensitizedPhone = "123****8910";
        assertTrue(jsonString.contains("\"phone\":\"" + desensitizedPhone + "\""));

        NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);
        assertEquals(desensitizedPhone, noticeReceiverVO.getPhone());

        CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
        assertEquals("12345678910", desensitizationMapCache.get(noticeReceiverVO.getId() + "_" + noticeReceiverVO.getPhone()));
    }

* get desensitizationMap cache
* @return desensitizationMap cache
*/
public static CommonCacheService<String, Object> getDesensitizationMapCache(){return DESENSITIZATION_MAP_CACHE;};

Choose a reason for hiding this comment

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

medium

The formatting of this method is inconsistent with the other getter methods in this class. For better readability and maintainability, please format it to follow the same style.

    public static CommonCacheService<String, Object> getDesensitizationMapCache() {
        return DESENSITIZATION_MAP_CACHE;
    }

int index = StrUtil.indexOf(email, '@');
emailDesensitization = index <= 1 ? email :
StrUtil.replace(email, 1, index, '*');
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();

Choose a reason for hiding this comment

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

medium

Casting jsonGenerator.getOutputContext().getCurrentValue() to NoticeReceiverVO creates a tight coupling between this serializer and the NoticeReceiverVO class. This makes the serializer less reusable and could lead to a ClassCastException if it's ever used on a property of a different class. While this might be acceptable for now, consider if a more decoupled approach is possible for future maintainability.

CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (StrUtil.isNotBlank(phone)){
phoneDesensitization = DesensitizedUtil.mobilePhone(phone);
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();

Choose a reason for hiding this comment

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

medium

Casting jsonGenerator.getOutputContext().getCurrentValue() to NoticeReceiverVO creates a tight coupling between this serializer and the NoticeReceiverVO class. This makes the serializer less reusable and could lead to a ClassCastException if it's ever used on a property of a different class. While this might be acceptable for now, consider if a more decoupled approach is possible for future maintainability.

Comment on lines +136 to +142
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}

Choose a reason for hiding this comment

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

medium

This logic for restoring sensitive data from cache performs a cache lookup twice for each field (once in the if condition and once to get the value). This is inefficient. You can improve this by getting the value once and then checking if it's not null.

This block of code is also duplicated in the sendTestMsg method (lines 279-287). To adhere to the DRY principle, consider extracting this logic into a private helper method to be called from both places. This would improve maintainability and reduce redundancy.

Suggested change
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail())) {
Object originalEmail = desensitizationMapCache.get(noticeReceiver.getId() + "_" + noticeReceiver.getEmail());
if (ObjectUtil.isNotEmpty(originalEmail)) {
noticeReceiver.setEmail((String) originalEmail);
}
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone())) {
Object originalPhone = desensitizationMapCache.get(noticeReceiver.getId() + "_" + noticeReceiver.getPhone());
if (ObjectUtil.isNotEmpty(originalPhone)) {
noticeReceiver.setPhone((String) originalPhone);
}
}

Comment on lines +479 to +484
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>

Choose a reason for hiding this comment

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

medium

For better dependency management and to avoid bloating all modules, it's recommended to add this dependency to the <dependencyManagement> section. This allows modules to include it on an as-needed basis. Also, consider using specific hutool artifacts (e.g., hutool-core) instead of hutool-all to minimize the application's dependency footprint.

@DDShantanuBadmanji
Copy link

/refacto-test

@refacto-test
Copy link

refacto-test bot commented Aug 11, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

@refacto-test
Copy link

refacto-test bot commented Aug 11, 2025

Code Review: Data Desensitization Implementation

👍 Well Done
Sensitive Data Protection

Proper implementation of data desensitization enhances security and privacy for email and phone numbers.

Cache Management

Effective caching of desensitized data prevents redundant processing while maintaining original values.

📌 Files Processed
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java
  • pom.xml
  • hertzbeat-common/pom.xml
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java
📝 Additional Comments
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (1)
Missing Cache Eviction

Cache entries are never explicitly removed after use. This could lead to memory leaks and unnecessary retention of sensitive data.

CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
String cacheEmailKey = noticeReceiver.getId()+"_"+noticeReceiver.getEmail();
String cachePhoneKey = noticeReceiver.getId()+"_"+noticeReceiver.getPhone();
try {
    if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(cacheEmailKey))){
        noticeReceiver.setEmail((String) desensitizationMapCache.get(cacheEmailKey));
    }
    if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(cachePhoneKey))){
        noticeReceiver.setPhone((String) desensitizationMapCache.get(cachePhoneKey));
    }
    noticeReceiverDao.save(noticeReceiver);
} finally {
    // Remove sensitive data from cache after use
    desensitizationMapCache.remove(cacheEmailKey);
    desensitizationMapCache.remove(cachePhoneKey);
}

Standards:

  • CWE-212
  • OWASP-A02

new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);

private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);
Copy link

Choose a reason for hiding this comment

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

Cache Expiration Risk

Sensitive data stored in cache for 1 day creates unnecessary exposure risk. Long-lived sensitive data cache increases potential compromise window if application is breached.

Suggested change
new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);
private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofMinutes(30), false);
Standards
  • CWE-922
  • OWASP-A02

Comment on lines +137 to +142
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
Copy link

Choose a reason for hiding this comment

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

Missing Cache Validation

Cache retrieval lacks null check on noticeReceiver.getId(). NullPointerException risk if ID is null, causing service failure during receiver editing.

Suggested change
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
if (noticeReceiver.getId() != null) {
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
}
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Precondition-Validation

Comment on lines +137 to +141
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
Copy link

Choose a reason for hiding this comment

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

Redundant Cache Lookups

Duplicate cache lookups for the same key cause unnecessary overhead. The cache is queried twice for each field - once for existence check and again for retrieval.

Suggested change
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
Object cachedEmail = ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) ? desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()) : null;
if (ObjectUtil.isNotEmpty(cachedEmail)) {
noticeReceiver.setEmail((String) cachedEmail);
}
Object cachedPhone = ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) ? desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()) : null;
if (ObjectUtil.isNotEmpty(cachedPhone)) {
noticeReceiver.setPhone((String) cachedPhone);
}
Standards
  • ISO-IEC-25010-Performance-Time-Behaviour
  • Cache-Access-Optimization
  • Algorithm-Opt-Redundancy-Elimination

Comment on lines +279 to +287
if (null != noticeReceiver.getId()){
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
}
Copy link

Choose a reason for hiding this comment

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

Duplicate Cache Logic

Duplicate desensitization logic in editReceiver and sendTestMsg methods creates maintenance burden. Code duplication increases risk of inconsistent behavior and future bugs.

Suggested change
if (null != noticeReceiver.getId()){
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
}
/**
* Restore desensitized fields from cache
* @param noticeReceiver The notice receiver to restore
*/
private void restoreDesensitizedFields(NoticeReceiver noticeReceiver) {
if (null != noticeReceiver.getId()) {
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
Object cachedEmail = ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) ? desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()) : null;
if (ObjectUtil.isNotEmpty(cachedEmail)) {
noticeReceiver.setEmail((String) cachedEmail);
}
Object cachedPhone = ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) ? desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()) : null;
if (ObjectUtil.isNotEmpty(cachedPhone)) {
noticeReceiver.setPhone((String) cachedPhone);
}
}
}
Standards
  • ISO-IEC-25010-Reliability-Maturity
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Code-Maintainability

Comment on lines +44 to +45
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
Copy link

Choose a reason for hiding this comment

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

Cache Key Collision

Cache key construction using desensitized value creates potential collisions when different emails produce identical desensitized patterns. This can cause incorrect data retrieval during restoration.

Suggested change
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_email", email);
Standards
  • ISO-IEC-25010-Performance-Resource-Utilization
  • Algorithm-Opt-Hash-Map
  • Cache-Key-Uniqueness

@visz11
Copy link
Collaborator

visz11 commented Sep 5, 2025

/refacto-test

@refacto-test
Copy link

refacto-test bot commented Sep 5, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

@refacto-test
Copy link

refacto-test bot commented Sep 5, 2025

Code Review: Desensitization Implementation

👍 Well Done
Comprehensive Desensitization Implementation

Well-structured serializers for protecting sensitive data with proper caching mechanism.

Caching Implementation

Using CacheFactory for desensitization mapping improves performance by avoiding repeated operations.

📌 Files Processed
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java
  • pom.xml
  • hertzbeat-common/pom.xml
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java
📝 Additional Comments
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java (1)
Incomplete Email Desensitization

Email desensitization logic doesn't handle edge cases properly. Very short usernames (index <= 1) remain unmasked, potentially exposing sensitive information.

            int index = StrUtil.indexOf(email, '@');
            if (index <= 1) {
                // Handle short usernames by masking at least part
                emailDesensitization = "*" + email.substring(index);
            } else {
                // Keep first character, mask the rest before @
                emailDesensitization = StrUtil.replace(email, 1, index, '*');
            }

Standards:

  • CWE-200
  • OWASP-A04
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (1)
Cache Retrieval Efficiency

Multiple cache lookups with the same key are performed for each field. This creates redundant cache access operations that could be optimized by retrieving the value once and reusing it.

        CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
        if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail())) {
            String emailKey = noticeReceiver.getId() + "_" + noticeReceiver.getEmail();
            Object originalEmail = desensitizationMapCache.get(emailKey);
            if (ObjectUtil.isNotEmpty(originalEmail)) {
                noticeReceiver.setEmail((String) originalEmail);
            }
        }
        if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone())) {
            String phoneKey = noticeReceiver.getId() + "_" + noticeReceiver.getPhone();
            Object originalPhone = desensitizationMapCache.get(phoneKey);
            if (ObjectUtil.isNotEmpty(originalPhone)) {
                noticeReceiver.setPhone((String) originalPhone);
            }
        }

Standards:

  • ISO-IEC-25010-Performance-Time-Behaviour
  • Netflix-Hot-Path-Optimization
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java (1)
Missing Error Handling

The serializer lacks error handling for potential null values or casting errors. If getCurrentValue() returns null or isn't a NoticeReceiverVO, it will cause runtime exceptions.

@Override
public void serialize(String phone, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    String phoneDesensitization = "";
    CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
    if (StrUtil.isNotBlank(phone)) {
        phoneDesensitization = DesensitizedUtil.mobilePhone(phone);
        try {
            Object currentObj = jsonGenerator.getOutputContext().getCurrentValue();
            if (currentObj instanceof NoticeReceiverVO) {
                NoticeReceiverVO currentValue = (NoticeReceiverVO) currentObj;
                if (currentValue.getId() != null) {
                    desensitizationMapCache.put(currentValue.getId() + "_" + phoneDesensitization, phone);
                }
            }
        } catch (Exception e) {
            // Log error but continue with desensitization
            // logger.warn("Failed to cache original value", e);
        }
    }
    jsonGenerator.writeString(phoneDesensitization);
}

Standards:

  • Clean-Code-Error-Handling
  • Clean-Code-Defensive-Programming
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java (1)
Test Case Improvement

Test case doesn't verify actual desensitization behavior. Missing verification that email is properly masked in serialized output affects reliability validation.

@Test
public void test() throws JsonProcessingException {
    NoticeReceiverVO noticeReceiver = new NoticeReceiverVO();
    noticeReceiver.setId(1L);
    String originalEmail = "[email protected]";
    noticeReceiver.setEmail(originalEmail);
    
    ObjectMapper objectMapper = new ObjectMapper();
    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addSerializer(String.class, emailDesensitizationSerializer);
    
    objectMapper.registerModule(simpleModule);
    
    String jsonString = objectMapper.writeValueAsString(noticeReceiver);
    NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);
    
    // Verify email is desensitized (masked)
    assertNotEquals(originalEmail, noticeReceiverVO.getEmail());
    assertTrue(noticeReceiverVO.getEmail().contains("*"));
    
    // Verify original value is in cache
    CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
    assertEquals(originalEmail, desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getEmail()));
}

Standards:

  • ISO-IEC-25010-Reliability-Testability
  • ISO-IEC-25010-Functional-Correctness-Verification

new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);

private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);
Copy link

Choose a reason for hiding this comment

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

Cache Expiration Risk

Desensitization cache uses a long expiration time (1 day) which increases sensitive data exposure risk. Attackers with access to the application could access sensitive data longer than necessary.

    private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
            new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofMinutes(30), false);
Standards
  • CWE-539
  • OWASP-A02

Comment on lines +44 to +45
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
Copy link

Choose a reason for hiding this comment

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

Cache Entry Leakage

Cache entries are created but never removed, potentially causing memory leaks over time. Long-lived cached sensitive data increases exposure risk and memory consumption.

// Add cache entry with expiration time
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
if (currentValue.getId() != null) {
    desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
}
Standards
  • ISO-IEC-25010-Reliability-Resource-Utilization
  • ISO-IEC-25010-Functional-Correctness-Appropriateness

Comment on lines +42 to +44
phoneDesensitization = DesensitizedUtil.mobilePhone(phone);
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
Copy link

Choose a reason for hiding this comment

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

Null ID Risk

No null check on currentValue.getId() before cache key creation. NullPointerException risk if serializing object with null ID, causing serialization failures.

phoneDesensitization = DesensitizedUtil.mobilePhone(phone);
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
if (currentValue.getId() != null) {
    desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
}
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Robustness

Comment on lines +44 to +45
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
Copy link

Choose a reason for hiding this comment

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

Cache Key Collision

Cache key uses desensitized email which creates collision risk when multiple receivers have similar emails like '[email protected]' and '[email protected]' (both masked as '*@example.com'). This would overwrite cache entries causing data retrieval errors.

NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
// Use a more unique key pattern to prevent collisions
desensitizationMapCache.put(currentValue.getId() + "_email", email);
Standards
  • Logic-Verification-Data-Integrity
  • Business-Rule-Unique-Key-Generation
  • Algorithm-Correctness-Cache-Management

Comment on lines +43 to +44
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
Copy link

Choose a reason for hiding this comment

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

Phone Desensitization Collision

Cache key uses desensitized phone which creates collision risk when multiple receivers have similar phone numbers that produce identical masked values. This would overwrite cache entries causing data retrieval errors.

NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
// Use a more unique key pattern to prevent collisions
desensitizationMapCache.put(currentValue.getId()+"_phone", phone);
Standards
  • Logic-Verification-Data-Integrity
  • Business-Rule-Unique-Key-Generation
  • Algorithm-Correctness-Cache-Management

Comment on lines +35 to +49
public class PhoneDesensitizationSerializer extends JsonSerializer<String> {

@Override
public void serialize(String phone, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
String phoneDesensitization = "";
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (StrUtil.isNotBlank(phone)){
phoneDesensitization = DesensitizedUtil.mobilePhone(phone);
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
}

jsonGenerator.writeString(phoneDesensitization);
}
} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

Duplicate Cache Logic

Similar caching logic is duplicated across Email and Phone serializers. This violates DRY principle and makes future changes error-prone as modifications must be synchronized across multiple classes.

public abstract class AbstractDesensitizationSerializer<T> extends JsonSerializer<String> {

    protected abstract String desensitize(String value);

    @Override
    public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        String desensitized = "";
        CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
        if (StrUtil.isNotBlank(value)) {
            desensitized = desensitize(value);
            NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
            desensitizationMapCache.put(currentValue.getId() + "_" + desensitized, value);
        }
        jsonGenerator.writeString(desensitized);
    }
}

public class EmailDesensitizationSerializer extends AbstractDesensitizationSerializer<String> {
    @Override
    protected String desensitize(String email) {
        int index = StrUtil.indexOf(email, '@');
        return index <= 1 ? email : StrUtil.replace(email, 1, index, '*');
    }
}

public class PhoneDesensitizationSerializer extends AbstractDesensitizationSerializer<String> {
    @Override
    protected String desensitize(String phone) {
        return DesensitizedUtil.mobilePhone(phone);
    }
}
Standards
  • SOLID-DRY
  • Clean-Code-Duplication
  • Design-Pattern-Template-Method

Comment on lines +37 to +38
private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);
Copy link

Choose a reason for hiding this comment

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

Cache Size Limitation

The DESENSITIZATION_MAP_CACHE is configured with a maximum size of 1000 entries and TTL of 1 day. For large systems with many users viewing sensitive data, this could lead to cache evictions and reduced performance. The cache size should be adjusted based on expected user load and sensitive data volume.

    private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
            new CaffeineCacheServiceImpl<>(10, 5000, Duration.ofHours(12), false);
Standards
  • ISO-IEC-25010-Performance-Resource-Utilization
  • Netflix-Multi-Layer-Caching

Comment on lines +136 to +142
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
Copy link

Choose a reason for hiding this comment

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

Inconsistent Desensitization Handling

Duplicate cache key construction logic creates maintenance risk. Inconsistent handling between serialization and deserialization could cause data integrity issues.

CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
Long id = noticeReceiver.getId();
if (id != null) {
    String email = noticeReceiver.getEmail();
    String phone = noticeReceiver.getPhone();
    String emailCacheKey = id + "_" + email;
    String phoneCacheKey = id + "_" + phone;
    
    if (ObjectUtil.isNotEmpty(email) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(emailCacheKey))) {
        noticeReceiver.setEmail((String) desensitizationMapCache.get(emailCacheKey));
    }
    if (ObjectUtil.isNotEmpty(phone) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(phoneCacheKey))) {
        noticeReceiver.setPhone((String) desensitizationMapCache.get(phoneCacheKey));
    }
}
Standards
  • ISO-IEC-25010-Reliability-Maturity
  • ISO-IEC-25010-Functional-Correctness-Consistency

@visz11
Copy link
Collaborator

visz11 commented Sep 5, 2025

/refacto-test

Comment on lines +23 to +40
@Test
public void test() throws JsonProcessingException {
NoticeReceiverVO noticeReceiver = new NoticeReceiverVO();
noticeReceiver.setId(1L);
noticeReceiver.setEmail("[email protected]");

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(String.class,emailDesensitizationSerializer);

objectMapper.registerModule(simpleModule);

String jsonString = objectMapper.writeValueAsString(noticeReceiver);
NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);

CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();

assertEquals(noticeReceiver.getEmail(),desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getEmail()));
Copy link

Choose a reason for hiding this comment

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

Duplicate Test Logic

Test logic is duplicated between EmailDesensitizationSerializerTest and PhoneDesensitizationSerializerTest. This violates DRY principle and increases maintenance burden when test approach needs to change.

// Create a base test class
abstract class AbstractDesensitizationSerializerTest<T extends JsonSerializer<String>> {
    protected T serializer;
    protected abstract T createSerializer();
    protected abstract void setupTestData(NoticeReceiverVO receiver);
    protected abstract String getFieldName();
    
    @BeforeEach
    void setUp() {
        serializer = createSerializer();
    }
    
    @Test
    public void testDesensitization() throws JsonProcessingException {
        NoticeReceiverVO noticeReceiver = new NoticeReceiverVO();
        noticeReceiver.setId(1L);
        setupTestData(noticeReceiver);
        
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(String.class, serializer);
        objectMapper.registerModule(simpleModule);
        
        String jsonString = objectMapper.writeValueAsString(noticeReceiver);
        NoticeReceiverVO noticeReceiverVO = objectMapper.readValue(jsonString, NoticeReceiverVO.class);
        
        CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
        String fieldValue = (String) PropertyUtils.getProperty(noticeReceiver, getFieldName());
        String desensitizedValue = (String) PropertyUtils.getProperty(noticeReceiverVO, getFieldName());
        
        assertEquals(fieldValue, desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+desensitizedValue));
    }
}
Standards
  • Clean-Code-DRY
  • Design-Pattern-Template-Method

@visz11
Copy link
Collaborator

visz11 commented Sep 5, 2025

/refacto-test

1 similar comment
@visz11
Copy link
Collaborator

visz11 commented Sep 5, 2025

/refacto-test

@refacto-test
Copy link

refacto-test bot commented Sep 5, 2025

⚠️ No Active Seat

The user @visz11 does not have an active seat allocated for code reviews.

Please contact your organization admin to assign a seat or upgrade your subscription.

1 similar comment
@refacto-test
Copy link

refacto-test bot commented Sep 5, 2025

⚠️ No Active Seat

The user @visz11 does not have an active seat allocated for code reviews.

Please contact your organization admin to assign a seat or upgrade your subscription.

@visz11
Copy link
Collaborator

visz11 commented Sep 5, 2025

/refacto-test

@refacto-test
Copy link

refacto-test bot commented Sep 5, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

@refacto-test
Copy link

refacto-test bot commented Sep 5, 2025

Code Review: Desensitization Implementation

👍 Well Done
Comprehensive Desensitization Strategy

Well-implemented email and phone masking with proper cache integration for data retrieval.

Caching Implementation

Efficient caching of desensitized data prevents redundant processing.

📌 Files Processed
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java
  • pom.xml
  • hertzbeat-common/pom.xml
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java
📝 Additional Comments
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java (3)
Incomplete Email Desensitization

Email desensitization preserves domain and first character. More complete desensitization would protect domain information. Partial email domain exposure could aid in targeted phishing attacks.

int index = StrUtil.indexOf(email, '@');
if (index <= 1) {
    emailDesensitization = email;
} else {
    String domain = email.substring(index);
    String username = email.substring(0, index);
    String maskedUsername = username.charAt(0) + "*".repeat(username.length() - 1);
    emailDesensitization = maskedUsername + domain.substring(0, Math.min(3, domain.length())) + "***";
}

Standards:

  • CWE-200
  • OWASP-A04
Inconsistent Error Handling

No error handling for potential ClassCastException if serializer is used with non-NoticeReceiverVO objects. This creates maintainability risk as the serializer is tightly coupled to NoticeReceiverVO.

public void serialize(String email, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    String emailDesensitization = "";
    CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
    if (StrUtil.isNotBlank(email)) {
        int index = StrUtil.indexOf(email, '@');
        emailDesensitization = index <= 1 ? email :
                StrUtil.replace(email, 1, index, '*');
        Object currentValue = jsonGenerator.getOutputContext().getCurrentValue();
        if (currentValue instanceof NoticeReceiverVO) {
            NoticeReceiverVO receiver = (NoticeReceiverVO) currentValue;
            desensitizationMapCache.put(receiver.getId() + "_" + emailDesensitization, email);
        }
    }
    jsonGenerator.writeString(emailDesensitization);

Standards:

  • Clean-Code-Error-Handling
  • SOLID-OCP
Potential ClassCastException Risk

Unchecked cast to NoticeReceiverVO could cause ClassCastException if serializer is used with incompatible types. This would cause runtime failures during serialization of non-NoticeReceiverVO objects.

Object currentValueObj = jsonGenerator.getOutputContext().getCurrentValue();
if (currentValueObj instanceof NoticeReceiverVO) {
    NoticeReceiverVO currentValue = (NoticeReceiverVO) currentValueObj;
    if (currentValue.getId() != null) {
        desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
    }
}

Standards:

  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (1)
Redundant Cache Logic

Duplicate desensitization logic exists in both editReceiver and sendTestMsg methods. This violates DRY principle and increases maintenance burden when logic needs updating in multiple places.

// Extract to a reusable method
private void applyDesensitizationFromCache(NoticeReceiver noticeReceiver) {
    if (null != noticeReceiver.getId()) {
        CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
        if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail())) {
            Object cachedEmail = desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail());
            if (ObjectUtil.isNotEmpty(cachedEmail)) {
                noticeReceiver.setEmail((String) cachedEmail);
            }
        }
        if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone())) {
            Object cachedPhone = desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone());
            if (ObjectUtil.isNotEmpty(cachedPhone)) {
                noticeReceiver.setPhone((String) cachedPhone);
            }
        }
    }
}

// Then call this method from both editReceiver and sendTestMsg

Standards:

  • Logic-Verification-Code-Reuse
  • Algorithm-Correctness-DRY-Principle
  • Business-Rule-Maintainability
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java (1)
Cache Size Management

The desensitization cache is configured with a 1-day TTL and maximum size of 1000 entries. For high-volume systems with many users, this could lead to cache evictions and unnecessary recomputation. Consider adjusting cache parameters based on expected user volume and access patterns.

    // Adjust cache size based on expected user volume and access patterns
    private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
            new CaffeineCacheServiceImpl<>(10, 10000, Duration.ofDays(1), false);

Standards:

  • ISO-IEC-25010-Performance-Resource-Utilization
  • Netflix-Multi-Layer-Caching

new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);

private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);
Copy link

Choose a reason for hiding this comment

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

Missing Cache Expiration

Sensitive data cache retains information for 1 day without explicit expiration control. Extended cache retention increases exposure window of sensitive information. Shorter TTL reduces risk window.

private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
            new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofMinutes(30), true);
Standards
  • CWE-524
  • OWASP-A04

Comment on lines +136 to +142
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
Copy link

Choose a reason for hiding this comment

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

Null Safety Issue

The code doesn't check if noticeReceiver.getId() is null before concatenation. This can cause NullPointerException when constructing cache keys, leading to runtime failures during desensitization operations.

CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (noticeReceiver.getId() != null) {
    if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
        noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
    }
    if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
        noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
    }
}
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness

Comment on lines +137 to +142
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
Copy link

Choose a reason for hiding this comment

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

Missing Cache Null-Check

Cache lookup is performed twice for each field, causing redundant cache access and potential inconsistency if cache changes between lookups. The retrieved value should be stored in a variable to ensure consistency.

// Store cache lookup results to avoid redundant access
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail())) {
    Object cachedEmail = desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail());
    if (ObjectUtil.isNotEmpty(cachedEmail)) {
        noticeReceiver.setEmail((String) cachedEmail);
    }
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone())) {
    Object cachedPhone = desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone());
    if (ObjectUtil.isNotEmpty(cachedPhone)) {
        noticeReceiver.setPhone((String) cachedPhone);
    }
}
Standards
  • Algorithm-Correctness-Cache-Access
  • Logic-Verification-Consistency
  • Business-Rule-Data-Integrity

Comment on lines +34 to +37
public class EmailDesensitizationSerializer extends JsonSerializer<String> {

@Override
public void serialize(String email, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
Copy link

Choose a reason for hiding this comment

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

Missing Javadoc Comments

Class and method lack Javadoc comments explaining purpose and behavior. Missing documentation reduces maintainability by making code intent and usage less clear for future developers.

/**
 * Custom serializer that desensitizes email addresses by replacing characters with '*'.
 * Stores original value in cache for retrieval when needed.
 */
public class EmailDesensitizationSerializer extends JsonSerializer<String> {

    /**
     * Serializes email by replacing characters between first character and '@' with '*'.
     * Stores the mapping between desensitized and original email in cache.
     *
     * @param email Email address to desensitize
     * @param jsonGenerator JSON generator
     * @param serializerProvider Serializer provider
     * @throws IOException If an I/O error occurs
     */
    @Override
    public void serialize(String email, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
Standards
  • Clean-Code-Documentation
  • Clean-Code-Readability

emailDesensitization = index <= 1 ? email :
StrUtil.replace(email, 1, index, '*');
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
Copy link

Choose a reason for hiding this comment

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

Potential Cache Leakage

Cache entries are created but never expired or cleaned up, potentially causing memory leaks as the cache grows unbounded over time. Without TTL or eviction policy, sensitive data remains in memory indefinitely.

// Add cache entry with TTL to prevent memory leaks
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email, Duration.ofMinutes(30));
Standards
  • Logic-Verification-Resource-Management
  • Algorithm-Correctness-Memory-Management
  • Business-Rule-Data-Protection

@visz11
Copy link
Collaborator

visz11 commented Sep 22, 2025

/refacto-test

@refacto-test
Copy link

refacto-test bot commented Sep 22, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

@refacto-test
Copy link

refacto-test bot commented Sep 22, 2025

Code Review: Desensitization Implementation

👍 Well Done
Comprehensive Desensitization Strategy

Well-structured serializers for protecting sensitive information with proper caching mechanism.

Test Coverage Added

Thorough test cases validate desensitization functionality ensuring reliability.

📌 Files Processed
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/entity/dto/vo/NoticeReceiverVO.java
  • pom.xml
  • hertzbeat-common/pom.xml
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializer.java
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializer.java
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java
  • hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/PhoneDesensitizationSerializerTest.java
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/controller/NoticeConfigController.java
  • hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java
  • hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java
📝 Additional Comments
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/cache/CacheFactory.java (2)
Improve Cache Security

The desensitization cache is configured with a long expiration time (1 day) and no encryption. For sensitive data like emails and phone numbers, shorter expiration periods and additional security measures should be considered to minimize exposure window if compromised.

Standards:

  • CWE-524
  • OWASP-A04
  • NIST-SSDF-PW.1
Cache Size Management

The desensitization cache is configured with a fixed maximum size of 1000 entries and TTL of 1 day. For systems with high user volume, this could lead to cache eviction and repeated desensitization processing. Consider making cache size configurable based on expected system load.

Standards:

  • ISO-IEC-25010-Performance-Efficiency-Resource-Utilization
  • Optimization-Pattern-Cache-Sizing
  • Memory-Allocation-Optimization
hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/NoticeConfigServiceImpl.java (2)
Improve Data Protection

The implementation retrieves sensitive data from cache without validation or access control checks. This approach could be improved by implementing a more secure method for handling sensitive data retrieval with proper authentication and authorization checks before accessing the original values.

Standards:

  • CWE-522
  • OWASP-A04
  • NIST-SSDF-PW.1
Duplicate Code Pattern

Duplicate desensitization restoration logic appears in both editReceiver and sendTestMsg methods. This creates maintenance challenges and increases error risk if logic needs to change. Extract to a common helper method to improve reliability through code reuse.

Standards:

  • ISO-IEC-25010-Maintainability-Reusability
  • DbC-Code-Organization
hertzbeat-common/src/test/java/org/apache/hertzbeat/common/serialize/EmailDesensitizationSerializerTest.java (1)
Duplicate Test Structure

Test structure is duplicated between EmailDesensitizationSerializerTest and PhoneDesensitizationSerializerTest with minimal differences. This violates DRY principle and increases maintenance burden. Consider extracting common test setup into a base test class or utility methods.

Standards:

  • Clean-Code-DRY
  • Maintainability-Quality-TestStructure
  • Refactoring-Extract-BaseTest

emailDesensitization = index <= 1 ? email :
StrUtil.replace(email, 1, index, '*');
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
Copy link

Choose a reason for hiding this comment

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

Cache Key Collision

Cache key construction using desensitized value creates potential collision risk. Multiple different emails could produce identical desensitized values, causing cache overwrite and incorrect data retrieval. This would lead to incorrect information being restored during edit operations.

            if (currentValue.getId() != null) {
                // Use original email in key to prevent collisions from different emails with same desensitized pattern
                desensitizationMapCache.put(currentValue.getId() + "_email_" + email, email);
            }
Commitable Suggestion
Suggested change
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
if (currentValue.getId() != null) {
// Use original email in key to prevent collisions from different emails with same desensitized pattern
desensitizationMapCache.put(currentValue.getId() + "_email_" + email, email);
}
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Data-Integrity

if (StrUtil.isNotBlank(phone)){
phoneDesensitization = DesensitizedUtil.mobilePhone(phone);
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
Copy link

Choose a reason for hiding this comment

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

Cache Key Collision

Cache key construction using desensitized value creates potential collision risk. Different phone numbers with same desensitized pattern could overwrite each other in cache. This would lead to incorrect information being restored during edit operations.

            if (currentValue.getId() != null) {
                // Use original phone in key to prevent collisions from different phones with same desensitized pattern
                desensitizationMapCache.put(currentValue.getId() + "_phone_" + phone, phone);
            }
Commitable Suggestion
Suggested change
desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
if (currentValue.getId() != null) {
// Use original phone in key to prevent collisions from different phones with same desensitized pattern
desensitizationMapCache.put(currentValue.getId() + "_phone_" + phone, phone);
}
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Data-Integrity

Comment on lines +44 to +45
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
Copy link

Choose a reason for hiding this comment

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

Null ID Handling

The code assumes currentValue.getId() is non-null when constructing the cache key, but there's no null check. If ID is null during serialization (e.g., for new unsaved entities), this will cause a NullPointerException when concatenating with the string.

            NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
            if (currentValue.getId() != null) {
                // Use a prefix and original email to ensure key uniqueness
                desensitizationMapCache.put(currentValue.getId() + "_email_" + email, email);
            }
Commitable Suggestion
Suggested change
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
if (currentValue.getId() != null) {
// Use a prefix and original email to ensure key uniqueness
desensitizationMapCache.put(currentValue.getId() + "_email_" + email, email);
}
Standards
  • Algorithm-Correctness-Null-Safety
  • Business-Rule-Error-Prevention
  • Logic-Verification-Defensive-Programming

Comment on lines +43 to +44
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
Copy link

Choose a reason for hiding this comment

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

Missing Null Check

No null check on currentValue or its getId() method before using in cache key. NullPointerException could occur if the context value is null or doesn't have a valid ID, causing serialization failure and potential data loss.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Defensive-Programming

Comment on lines +41 to +43
int index = StrUtil.indexOf(email, '@');
emailDesensitization = index <= 1 ? email :
StrUtil.replace(email, 1, index, '*');
Copy link

Choose a reason for hiding this comment

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

Email Desensitization Logic

The email desensitization logic has an edge case where emails with only one character before @ (e.g., [email protected]) won't be desensitized at all. This contradicts the desensitization purpose and creates inconsistent behavior between similar email addresses.

Standards
  • Algorithm-Correctness-Edge-Cases
  • Business-Rule-Consistency
  • Logic-Verification-Boundary-Conditions

Comment on lines +43 to +45
StrUtil.replace(email, 1, index, '*');
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
Copy link

Choose a reason for hiding this comment

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

Cache Security Risk

Sensitive email data is stored in a cache without expiration controls or access restrictions. This creates a potential data exposure risk if the cache is compromised or accessed by unauthorized code. The original sensitive data remains accessible through the cache indefinitely.

Standards
  • CWE-524
  • OWASP-A04
  • NIST-SSDF-PW.1

Comment on lines +42 to +44
phoneDesensitization = DesensitizedUtil.mobilePhone(phone);
NoticeReceiverVO currentValue = (NoticeReceiverVO)jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId()+"_"+phoneDesensitization, phone);
Copy link

Choose a reason for hiding this comment

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

Cache Security Risk

Sensitive phone number data is stored in a cache without expiration controls or access restrictions. This creates a potential data exposure risk if the cache is compromised or accessed by unauthorized code. The original sensitive data remains accessible through the cache indefinitely.

Standards
  • CWE-524
  • OWASP-A04
  • NIST-SSDF-PW.1

Comment on lines +136 to +142
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
Copy link

Choose a reason for hiding this comment

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

Duplicate Desensitization Logic

Duplicate desensitization logic appears in both editReceiver and sendTestMsg methods. This violates DRY principle and increases maintenance burden when desensitization logic changes. Extract this common functionality into a separate method for better maintainability.

Standards
  • Clean-Code-DRY
  • Refactoring-Extract-Method
  • Maintainability-Quality-Duplication

new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);

private static final CommonCacheService<String, Object> DESENSITIZATION_MAP_CACHE =
new CaffeineCacheServiceImpl<>(10, 1000, Duration.ofDays(1), false);
Copy link

Choose a reason for hiding this comment

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

Cache Eviction Strategy

Desensitization cache uses fixed 1-day expiration without explicit eviction strategy. This could lead to memory pressure if many records are processed. Consider implementing a more aggressive expiration policy or size-based eviction.

Standards
  • ISO-IEC-25010-Reliability-Resource-Utilization
  • SRE-Resource-Management

Comment on lines +136 to +142
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (ObjectUtil.isNotEmpty(noticeReceiver.getEmail()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()))){
noticeReceiver.setEmail((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getEmail()));
}
if (ObjectUtil.isNotEmpty(noticeReceiver.getPhone()) && ObjectUtil.isNotEmpty(desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()))){
noticeReceiver.setPhone((String) desensitizationMapCache.get(noticeReceiver.getId()+"_"+noticeReceiver.getPhone()));
}
Copy link

Choose a reason for hiding this comment

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

Inconsistent Desensitization Logic

Desensitization restoration logic depends on cache having exact match for desensitized values. If cache expires or is cleared, original values cannot be restored. This creates a reliability dependency on cache state for critical functionality.

Standards
  • ISO-IEC-25010-Reliability-Recoverability
  • ISO-IEC-25010-Functional-Correctness-Appropriateness

@@ -0,0 +1,42 @@
package org.apache.hertzbeat.common.serialize;
Copy link

Choose a reason for hiding this comment

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

Missing License Header

Missing Apache License header violates project standards for source code files. This inconsistency makes license compliance tracking difficult and could cause legal issues. All source files should include the standard Apache License header for consistency.

Standards
  • Infringement-License-Header
  • Maintainability-Quality-Consistency
  • Clean-Code-Standardization

@@ -0,0 +1,42 @@
package org.apache.hertzbeat.common.serialize;
Copy link

Choose a reason for hiding this comment

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

Missing License Header

Missing Apache License header violates project standards for source code files. This inconsistency makes license compliance tracking difficult and could cause legal issues. All source files should include the standard Apache License header for consistency.

Standards
  • Infringement-License-Header
  • Maintainability-Quality-Consistency
  • Clean-Code-Standardization


CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();

assertEquals(noticeReceiver.getEmail(),desensitizationMapCache.get(noticeReceiverVO.getId()+"_"+noticeReceiverVO.getEmail()));
Copy link

Choose a reason for hiding this comment

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

Test Assertion Validity

The test assertion compares the original email with the cached value, but noticeReceiverVO.getEmail() returns the desensitized email, not the original. This creates a logical inconsistency in the test as it's comparing using a key that doesn't match how keys are actually constructed in the serializer.

Standards
  • Algorithm-Correctness-Test-Logic
  • Business-Rule-Test-Validity
  • Logic-Verification-Test-Consistency

Comment on lines +38 to +47
String emailDesensitization = "";
CommonCacheService<String, Object> desensitizationMapCache = CacheFactory.getDesensitizationMapCache();
if (StrUtil.isNotBlank(email)) {
int index = StrUtil.indexOf(email, '@');
emailDesensitization = index <= 1 ? email :
StrUtil.replace(email, 1, index, '*');
NoticeReceiverVO currentValue = (NoticeReceiverVO) jsonGenerator.getOutputContext().getCurrentValue();
desensitizationMapCache.put(currentValue.getId() + "_" + emailDesensitization, email);
}
jsonGenerator.writeString(emailDesensitization);
Copy link

Choose a reason for hiding this comment

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

Inconsistent Null Handling

The code handles null/blank emails by setting emailDesensitization to an empty string, but doesn't add anything to the cache. This creates inconsistent behavior where desensitized empty values can't be reversed through the cache lookup mechanism used elsewhere.

Standards
  • Algorithm-Correctness-Null-Handling
  • Business-Rule-Consistency
  • Logic-Verification-Edge-Cases

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants