This is an automated email from the ASF dual-hosted git repository.
tomsun28 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push:
new c9e3338c9f [improve] mask secret fields of notice receiver in rest api
response (#4220)
c9e3338c9f is described below
commit c9e3338c9f287753aced7ee498969ad0b8420f3d
Author: Duansg <[email protected]>
AuthorDate: Tue Jul 21 23:11:30 2026 +0800
[improve] mask secret fields of notice receiver in rest api response (#4220)
---
.../alert/controller/NoticeConfigController.java | 9 +-
.../service/impl/NoticeConfigServiceImpl.java | 16 +++
.../alert/util/NoticeReceiverMaskUtil.java | 125 +++++++++++++++++++
.../controller/NoticeConfigControllerTest.java | 5 +
.../alert/service/NoticeConfigServiceTest.java | 35 ++++++
.../alert/util/NoticeReceiverMaskUtilTest.java | 134 +++++++++++++++++++++
.../common/entity/alerter/NoticeReceiver.java | 2 +-
7 files changed, 322 insertions(+), 4 deletions(-)
diff --git
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/controller/NoticeConfigController.java
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/controller/NoticeConfigController.java
index 7a349284ff..151fc0b12f 100644
---
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/controller/NoticeConfigController.java
+++
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/controller/NoticeConfigController.java
@@ -30,6 +30,7 @@ import
org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.alert.service.NoticeConfigService;
+import org.apache.hertzbeat.alert.util.NoticeReceiverMaskUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
@@ -87,14 +88,16 @@ public class NoticeConfigController {
@Parameter(description = "en: Recipient name,support fuzzy query",
example = "tom") @RequestParam(required = false) final String name,
@Parameter(description = "en: List current page", example = "0")
@RequestParam(defaultValue = "0") final int pageIndex,
@Parameter(description = "en: Number of list pages", example =
"8") @RequestParam(defaultValue = "8") final int pageSize) {
- return
ResponseEntity.ok(Message.success(noticeConfigService.getNoticeReceivers(name,
pageIndex, pageSize)));
+ return
ResponseEntity.ok(Message.success(noticeConfigService.getNoticeReceivers(name,
pageIndex, pageSize)
+ .map(NoticeReceiverMaskUtil::mask)));
}
@GetMapping(path = "/receivers/all")
@Operation(summary = "Get a list of all message notification recipients",
description = "Get a list of all message notification recipients")
public ResponseEntity<Message<List<NoticeReceiver>>> getAllReceivers() {
- return
ResponseEntity.ok(Message.success(noticeConfigService.getAllNoticeReceivers()));
+ return
ResponseEntity.ok(Message.success(noticeConfigService.getAllNoticeReceivers().stream()
+ .map(NoticeReceiverMaskUtil::mask).toList()));
}
@GetMapping(path = "/receiver/{id}")
@@ -106,7 +109,7 @@ public class NoticeConfigController {
if (noticeReceiver == null) {
return ResponseEntity.ok(Message.fail(FAIL_CODE, "The relevant
information of the recipient could not be found, please check whether the
parameters are correct or refresh the page"));
}
- return ResponseEntity.ok(Message.success(noticeReceiver));
+ return
ResponseEntity.ok(Message.success(NoticeReceiverMaskUtil.mask(noticeReceiver)));
}
@PostMapping(path = "/rule")
diff --git
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/NoticeConfigServiceImpl.java
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/NoticeConfigServiceImpl.java
index 24a719b5bf..2a16f39ab3 100644
---
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/NoticeConfigServiceImpl.java
+++
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/NoticeConfigServiceImpl.java
@@ -31,6 +31,7 @@ import org.apache.hertzbeat.alert.dao.NoticeReceiverDao;
import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
import org.apache.hertzbeat.alert.service.NoticeConfigService;
+import org.apache.hertzbeat.alert.util.NoticeReceiverMaskUtil;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
@@ -176,9 +177,23 @@ public class NoticeConfigServiceImpl implements
NoticeConfigService, CommandLine
@Override
public void editReceiver(NoticeReceiver noticeReceiver) {
+ resolveMaskedSecrets(noticeReceiver);
noticeReceiverDao.save(noticeReceiver);
}
+ /**
+ * The rest api returns receivers with masked secret fields, so a receiver
submitted
+ * from the ui may carry the mask placeholder instead of the real secret.
+ * Restore such fields from the stored entity before using the receiver.
+ */
+ private void resolveMaskedSecrets(NoticeReceiver noticeReceiver) {
+ if (noticeReceiver == null || noticeReceiver.getId() == null) {
+ return;
+ }
+ noticeReceiverDao.findById(noticeReceiver.getId())
+ .ifPresent(existing ->
NoticeReceiverMaskUtil.resolveMask(noticeReceiver, existing));
+ }
+
@Override
public void deleteReceiver(Long receiverId) {
noticeReceiverDao.deleteById(receiverId);
@@ -319,6 +334,7 @@ public class NoticeConfigServiceImpl implements
NoticeConfigService, CommandLine
@Override
public boolean sendTestMsg(NoticeReceiver noticeReceiver) {
+ resolveMaskedSecrets(noticeReceiver);
Map<String, String> labels = new HashMap<>(8);
labels.put(CommonConstants.LABEL_INSTANCE, "127.0.0.1");
labels.put(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert");
diff --git
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/NoticeReceiverMaskUtil.java
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/NoticeReceiverMaskUtil.java
new file mode 100644
index 0000000000..db5fe30a43
--- /dev/null
+++
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/NoticeReceiverMaskUtil.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.util;
+
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
+
+/**
+ * Masks the secret fields of {@link NoticeReceiver} before it is exposed
through the rest api,
+ * and resolves masked values back to the stored secrets when an edited
receiver is submitted.
+ * A secret long enough keeps its last characters visible so different tokens
stay
+ * distinguishable in the ui, while an unchanged secret round-trips through
the ui
+ * without ever leaving the server.
+ */
+public final class NoticeReceiverMaskUtil {
+
+ /**
+ * Fixed-length placeholder replacing the hidden part of a secret. Its
length is
+ * constant on purpose so the mask never reveals how long the real secret
is.
+ */
+ public static final String SECRET_MASK = "******";
+
+ /**
+ * A secret shorter than this is masked entirely: revealing a suffix of a
short
+ * secret would give away too large a fraction of it.
+ */
+ private static final int MIN_LENGTH_TO_SHOW_SUFFIX = 12;
+
+ /**
+ * Number of trailing characters kept visible for a long secret,
+ * enough to tell configured tokens apart.
+ */
+ private static final int VISIBLE_SUFFIX_LENGTH = 4;
+
+ private record SecretField(Function<NoticeReceiver, String> getter,
BiConsumer<NoticeReceiver, String> setter) {
+ }
+
+ private static final List<SecretField> SECRET_FIELDS = List.of(
+ new SecretField(NoticeReceiver::getHookAuthToken,
NoticeReceiver::setHookAuthToken),
+ new SecretField(NoticeReceiver::getAccessToken,
NoticeReceiver::setAccessToken),
+ new SecretField(NoticeReceiver::getTgBotToken,
NoticeReceiver::setTgBotToken),
+ new SecretField(NoticeReceiver::getSlackWebHookUrl,
NoticeReceiver::setSlackWebHookUrl),
+ new SecretField(NoticeReceiver::getAppSecret,
NoticeReceiver::setAppSecret),
+ new SecretField(NoticeReceiver::getDiscordBotToken,
NoticeReceiver::setDiscordBotToken),
+ new SecretField(NoticeReceiver::getSmnAk,
NoticeReceiver::setSmnAk),
+ new SecretField(NoticeReceiver::getSmnSk,
NoticeReceiver::setSmnSk),
+ new SecretField(NoticeReceiver::getServerChanToken,
NoticeReceiver::setServerChanToken),
+ new SecretField(NoticeReceiver::getGotifyToken,
NoticeReceiver::setGotifyToken),
+ new SecretField(NoticeReceiver::getNtfyToken,
NoticeReceiver::setNtfyToken));
+
+ private NoticeReceiverMaskUtil() {
+ }
+
+ /**
+ * Return a copy of the receiver with all secret fields masked.
+ * The given entity is not modified as it may still be attached to the
persistence context.
+ * @param receiver receiver to mask, may be null
+ * @return masked copy, or null if receiver is null
+ */
+ public static NoticeReceiver mask(NoticeReceiver receiver) {
+ if (receiver == null) {
+ return null;
+ }
+ NoticeReceiver masked = receiver.toBuilder().build();
+ for (SecretField field : SECRET_FIELDS) {
+ field.setter().accept(masked,
maskValue(field.getter().apply(masked)));
+ }
+ return masked;
+ }
+
+ /**
+ * Replace every secret field of the incoming receiver that still holds
the masked form
+ * of the stored secret with the stored value, so an edit that did not
touch a secret keeps it.
+ * A re-entered secret or a cleared field is left untouched.
+ * @param incoming receiver submitted by the ui, modified in place
+ * @param existing receiver currently stored in the database
+ */
+ public static void resolveMask(NoticeReceiver incoming, NoticeReceiver
existing) {
+ if (incoming == null || existing == null) {
+ return;
+ }
+ for (SecretField field : SECRET_FIELDS) {
+ String submitted = field.getter().apply(incoming);
+ String stored = field.getter().apply(existing);
+ if (isMaskOf(submitted, stored)) {
+ field.setter().accept(incoming, stored);
+ }
+ }
+ }
+
+ private static boolean isMaskOf(String submitted, String stored) {
+ if (submitted == null || StringUtils.isBlank(stored)) {
+ return false;
+ }
+ return submitted.equals(SECRET_MASK) ||
submitted.equals(maskValue(stored));
+ }
+
+ private static String maskValue(String value) {
+ if (StringUtils.isBlank(value)) {
+ return value;
+ }
+ if (value.length() < MIN_LENGTH_TO_SHOW_SUFFIX) {
+ return SECRET_MASK;
+ }
+ return SECRET_MASK + value.substring(value.length() -
VISIBLE_SUFFIX_LENGTH);
+ }
+}
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/controller/NoticeConfigControllerTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/controller/NoticeConfigControllerTest.java
index 84ca807bcb..f82556c822 100644
---
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/controller/NoticeConfigControllerTest.java
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/controller/NoticeConfigControllerTest.java
@@ -31,6 +31,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.apache.hertzbeat.alert.service.impl.NoticeConfigServiceImpl;
+import org.apache.hertzbeat.alert.util.NoticeReceiverMaskUtil;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
@@ -174,6 +175,7 @@ class NoticeConfigControllerTest {
NoticeReceiver receiver1 = new NoticeReceiver();
receiver1.setId(1L);
receiver1.setName("Receiver1");
+ receiver1.setTgBotToken("1499012345:AAEOB_wEYS-DZyPM3h5NzI8voJM");
NoticeReceiver receiver2 = new NoticeReceiver();
receiver2.setId(2L);
@@ -197,6 +199,7 @@ class NoticeConfigControllerTest {
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.content[0].id").value(1))
.andExpect(jsonPath("$.data.content[0].name").value("Receiver1"))
+
.andExpect(jsonPath("$.data.content[0].tgBotToken").value(NoticeReceiverMaskUtil.SECRET_MASK
+ "voJM"))
.andExpect(jsonPath("$.data.content[1].id").value(2))
.andExpect(jsonPath("$.data.content[1].name").value("Receiver2"))
.andExpect(jsonPath("$.data.totalElements").value(2))
@@ -222,6 +225,8 @@ class NoticeConfigControllerTest {
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receiver/{id}",
7565463543L))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int)
CommonConstants.SUCCESS_CODE))
+
.andExpect(jsonPath("$.data.accessToken").value(NoticeReceiverMaskUtil.SECRET_MASK
+ "739d"))
+ .andExpect(jsonPath("$.data.email").value("[email protected]"))
.andReturn();
}
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/NoticeConfigServiceTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/NoticeConfigServiceTest.java
index 4206140963..9b2780ba1e 100644
---
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/NoticeConfigServiceTest.java
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/NoticeConfigServiceTest.java
@@ -22,6 +22,7 @@ import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
import org.apache.hertzbeat.alert.service.impl.NoticeConfigServiceImpl;
+import org.apache.hertzbeat.alert.util.NoticeReceiverMaskUtil;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
@@ -48,6 +49,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -216,6 +218,39 @@ class NoticeConfigServiceTest {
verify(noticeReceiverDao, times(1)).save(noticeReceiver);
}
+ @Test
+ void editReceiverKeepsStoredSecretWhenMasked() {
+ final NoticeReceiver stored = new NoticeReceiver();
+ stored.setId(5L);
+ stored.setTgBotToken("1499012345:AAEOB_wEYS-DZyPM3h5NzI8voJM");
+ when(noticeReceiverDao.findById(5L)).thenReturn(Optional.of(stored));
+
+ final NoticeReceiver incoming = new NoticeReceiver();
+ incoming.setId(5L);
+ incoming.setTgBotToken(NoticeReceiverMaskUtil.SECRET_MASK + "voJM");
+
+ noticeConfigService.editReceiver(incoming);
+
+ assertEquals("1499012345:AAEOB_wEYS-DZyPM3h5NzI8voJM",
incoming.getTgBotToken());
+ verify(noticeReceiverDao, times(1)).save(incoming);
+ }
+
+ @Test
+ void sendTestMsgResolvesMaskedSecret() {
+ final NoticeReceiver stored = new NoticeReceiver();
+ stored.setId(5L);
+ stored.setTgBotToken("1499012345:AAEOB_wEYS-DZyPM3h5NzI8voJM");
+ when(noticeReceiverDao.findById(5L)).thenReturn(Optional.of(stored));
+
+ final NoticeReceiver incoming = new NoticeReceiver();
+ incoming.setId(5L);
+ incoming.setTgBotToken(NoticeReceiverMaskUtil.SECRET_MASK + "voJM");
+
+ noticeConfigService.sendTestMsg(incoming);
+
+ assertEquals("1499012345:AAEOB_wEYS-DZyPM3h5NzI8voJM",
incoming.getTgBotToken());
+ }
+
@Test
void deleteReceiver() {
final Long receiverId = 23342525L;
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/util/NoticeReceiverMaskUtilTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/util/NoticeReceiverMaskUtilTest.java
new file mode 100644
index 0000000000..7d42ed6e87
--- /dev/null
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/util/NoticeReceiverMaskUtilTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.util;
+
+import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
+import org.junit.jupiter.api.Test;
+
+import static
org.apache.hertzbeat.alert.util.NoticeReceiverMaskUtil.SECRET_MASK;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * Test case for {@link NoticeReceiverMaskUtil}
+ */
+class NoticeReceiverMaskUtilTest {
+
+ private NoticeReceiver buildReceiverWithSecrets() {
+ NoticeReceiver receiver = new NoticeReceiver();
+ receiver.setId(1L);
+ receiver.setName("tom");
+ receiver.setEmail("[email protected]");
+ receiver.setHookUrl("https://example.com/hook");
+ receiver.setHookAuthToken("hook-auth-token-abcd");
+
receiver.setAccessToken("c03a568a306f8fd84dab51ff03cf6af6ba676a3be940c904e1df2de34853739d");
+ receiver.setTgBotToken("1499012345:AAEOB_wEYS-DZyPM3h5NzI8voJM");
+
receiver.setSlackWebHookUrl("https://hooks.slack.com/services/X/Y/Zt0k3n");
+ receiver.setAppSecret("oUydwn92ey0lnuY02MixNa57eNK-20dJn5NEOG-u2uE");
+ receiver.setDiscordBotToken("MTA2NTMwMzU0ODY4Mzg4MjUzNw.discord.t0kn");
+ receiver.setSmnAk("NCVBODJOEYHSW3VNSMAK");
+ receiver.setSmnSk("nmSNhUJN9MlpPl8lfCsgdA0KvHCL9JSMSK");
+ receiver.setServerChanToken("SCT193569TSNm6xIabdjqeZPtOGOWcvU1e");
+ receiver.setGotifyToken("A845h__ZMqDxZlO");
+ receiver.setNtfyToken("tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2");
+ return receiver;
+ }
+
+ @Test
+ void maskKeepsSuffixOfLongSecrets() {
+ NoticeReceiver receiver = buildReceiverWithSecrets();
+ NoticeReceiver masked = NoticeReceiverMaskUtil.mask(receiver);
+
+ assertEquals(SECRET_MASK + "abcd", masked.getHookAuthToken());
+ assertEquals(SECRET_MASK + "739d", masked.getAccessToken());
+ assertEquals(SECRET_MASK + "voJM", masked.getTgBotToken());
+ assertEquals(SECRET_MASK + "0k3n", masked.getSlackWebHookUrl());
+ assertEquals(SECRET_MASK + "u2uE", masked.getAppSecret());
+ assertEquals(SECRET_MASK + "t0kn", masked.getDiscordBotToken());
+ assertEquals(SECRET_MASK + "SMAK", masked.getSmnAk());
+ assertEquals(SECRET_MASK + "SMSK", masked.getSmnSk());
+ assertEquals(SECRET_MASK + "vU1e", masked.getServerChanToken());
+ assertEquals(SECRET_MASK + "xZlO", masked.getGotifyToken());
+ assertEquals(SECRET_MASK + "NIz2", masked.getNtfyToken());
+
+ assertEquals(receiver.getId(), masked.getId());
+ assertEquals(receiver.getName(), masked.getName());
+ assertEquals(receiver.getEmail(), masked.getEmail());
+ assertEquals(receiver.getHookUrl(), masked.getHookUrl());
+ }
+
+ @Test
+ void maskHidesShortSecretsEntirely() {
+ NoticeReceiver receiver = new NoticeReceiver();
+ receiver.setAccessToken("short-token");
+ receiver.setGotifyToken("tiny");
+ NoticeReceiver masked = NoticeReceiverMaskUtil.mask(receiver);
+
+ assertEquals(SECRET_MASK, masked.getAccessToken());
+ assertEquals(SECRET_MASK, masked.getGotifyToken());
+ }
+
+ @Test
+ void maskDoesNotModifyOriginalAndKeepsEmptySecrets() {
+ NoticeReceiver receiver = buildReceiverWithSecrets();
+ NoticeReceiverMaskUtil.mask(receiver);
+
+ assertEquals("1499012345:AAEOB_wEYS-DZyPM3h5NzI8voJM",
receiver.getTgBotToken());
+ assertEquals("nmSNhUJN9MlpPl8lfCsgdA0KvHCL9JSMSK",
receiver.getSmnSk());
+
+ NoticeReceiver empty = new NoticeReceiver();
+ NoticeReceiver maskedEmpty = NoticeReceiverMaskUtil.mask(empty);
+ assertNull(maskedEmpty.getAccessToken());
+ assertNull(maskedEmpty.getNtfyToken());
+ assertNull(NoticeReceiverMaskUtil.mask(null));
+ }
+
+ @Test
+ void resolveMaskRestoresOnlyMaskedFields() {
+ NoticeReceiver existing = buildReceiverWithSecrets();
+ NoticeReceiver incoming = NoticeReceiverMaskUtil.mask(existing);
+ incoming.setAccessToken("new-access-token-1234");
+ incoming.setGotifyToken(null);
+ incoming.setNtfyToken(SECRET_MASK);
+
+ NoticeReceiverMaskUtil.resolveMask(incoming, existing);
+
+ assertEquals("new-access-token-1234", incoming.getAccessToken());
+ assertNull(incoming.getGotifyToken());
+ assertEquals("tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2",
incoming.getNtfyToken());
+ assertEquals("hook-auth-token-abcd", incoming.getHookAuthToken());
+ assertEquals("1499012345:AAEOB_wEYS-DZyPM3h5NzI8voJM",
incoming.getTgBotToken());
+ assertEquals("https://hooks.slack.com/services/X/Y/Zt0k3n",
incoming.getSlackWebHookUrl());
+ assertEquals("oUydwn92ey0lnuY02MixNa57eNK-20dJn5NEOG-u2uE",
incoming.getAppSecret());
+ assertEquals("MTA2NTMwMzU0ODY4Mzg4MjUzNw.discord.t0kn",
incoming.getDiscordBotToken());
+ assertEquals("NCVBODJOEYHSW3VNSMAK", incoming.getSmnAk());
+ assertEquals("nmSNhUJN9MlpPl8lfCsgdA0KvHCL9JSMSK",
incoming.getSmnSk());
+ assertEquals("SCT193569TSNm6xIabdjqeZPtOGOWcvU1e",
incoming.getServerChanToken());
+ }
+
+ @Test
+ void resolveMaskIgnoresMaskWhenNothingIsStored() {
+ NoticeReceiver existing = new NoticeReceiver();
+ NoticeReceiver incoming = new NoticeReceiver();
+ incoming.setAccessToken(SECRET_MASK);
+
+ NoticeReceiverMaskUtil.resolveMask(incoming, existing);
+
+ assertEquals(SECRET_MASK, incoming.getAccessToken());
+ }
+}
diff --git
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/alerter/NoticeReceiver.java
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/alerter/NoticeReceiver.java
index cc7dcd6d6b..e31a1ac29c 100644
---
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/alerter/NoticeReceiver.java
+++
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/alerter/NoticeReceiver.java
@@ -48,7 +48,7 @@ import
org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Entity
@Table(name = "hzb_notice_receiver")
@Data
-@Builder
+@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Message notification recipient entity")
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]