mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 09:40:58 +00:00
[improve] mask secret fields of notice receiver in rest api response (#4220)
This commit is contained in:
+6
-3
@@ -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.NoticeRule;
|
||||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||||
import org.apache.hertzbeat.alert.service.NoticeConfigService;
|
import org.apache.hertzbeat.alert.service.NoticeConfigService;
|
||||||
|
import org.apache.hertzbeat.alert.util.NoticeReceiverMaskUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.http.ResponseEntity;
|
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: 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: 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) {
|
@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")
|
@GetMapping(path = "/receivers/all")
|
||||||
@Operation(summary = "Get a list of all message notification recipients",
|
@Operation(summary = "Get a list of all message notification recipients",
|
||||||
description = "Get a list of all message notification recipients")
|
description = "Get a list of all message notification recipients")
|
||||||
public ResponseEntity<Message<List<NoticeReceiver>>> getAllReceivers() {
|
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}")
|
@GetMapping(path = "/receiver/{id}")
|
||||||
@@ -106,7 +109,7 @@ public class NoticeConfigController {
|
|||||||
if (noticeReceiver == null) {
|
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.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")
|
@PostMapping(path = "/rule")
|
||||||
|
|||||||
+16
@@ -31,6 +31,7 @@ import org.apache.hertzbeat.alert.dao.NoticeReceiverDao;
|
|||||||
import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
|
import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
|
||||||
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
|
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
|
||||||
import org.apache.hertzbeat.alert.service.NoticeConfigService;
|
import org.apache.hertzbeat.alert.service.NoticeConfigService;
|
||||||
|
import org.apache.hertzbeat.alert.util.NoticeReceiverMaskUtil;
|
||||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.CommandLineRunner;
|
import org.springframework.boot.CommandLineRunner;
|
||||||
@@ -176,9 +177,23 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void editReceiver(NoticeReceiver noticeReceiver) {
|
public void editReceiver(NoticeReceiver noticeReceiver) {
|
||||||
|
resolveMaskedSecrets(noticeReceiver);
|
||||||
noticeReceiverDao.save(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
|
@Override
|
||||||
public void deleteReceiver(Long receiverId) {
|
public void deleteReceiver(Long receiverId) {
|
||||||
noticeReceiverDao.deleteById(receiverId);
|
noticeReceiverDao.deleteById(receiverId);
|
||||||
@@ -319,6 +334,7 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sendTestMsg(NoticeReceiver noticeReceiver) {
|
public boolean sendTestMsg(NoticeReceiver noticeReceiver) {
|
||||||
|
resolveMaskedSecrets(noticeReceiver);
|
||||||
Map<String, String> labels = new HashMap<>(8);
|
Map<String, String> labels = new HashMap<>(8);
|
||||||
labels.put(CommonConstants.LABEL_INSTANCE, "127.0.0.1");
|
labels.put(CommonConstants.LABEL_INSTANCE, "127.0.0.1");
|
||||||
labels.put(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert");
|
labels.put(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert");
|
||||||
|
|||||||
+125
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -31,6 +31,7 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.apache.hertzbeat.alert.service.impl.NoticeConfigServiceImpl;
|
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.constants.CommonConstants;
|
||||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||||
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
|
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
|
||||||
@@ -174,6 +175,7 @@ class NoticeConfigControllerTest {
|
|||||||
NoticeReceiver receiver1 = new NoticeReceiver();
|
NoticeReceiver receiver1 = new NoticeReceiver();
|
||||||
receiver1.setId(1L);
|
receiver1.setId(1L);
|
||||||
receiver1.setName("Receiver1");
|
receiver1.setName("Receiver1");
|
||||||
|
receiver1.setTgBotToken("1499012345:AAEOB_wEYS-DZyPM3h5NzI8voJM");
|
||||||
|
|
||||||
NoticeReceiver receiver2 = new NoticeReceiver();
|
NoticeReceiver receiver2 = new NoticeReceiver();
|
||||||
receiver2.setId(2L);
|
receiver2.setId(2L);
|
||||||
@@ -197,6 +199,7 @@ class NoticeConfigControllerTest {
|
|||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.data.content[0].id").value(1))
|
.andExpect(jsonPath("$.data.content[0].id").value(1))
|
||||||
.andExpect(jsonPath("$.data.content[0].name").value("Receiver1"))
|
.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].id").value(2))
|
||||||
.andExpect(jsonPath("$.data.content[1].name").value("Receiver2"))
|
.andExpect(jsonPath("$.data.content[1].name").value("Receiver2"))
|
||||||
.andExpect(jsonPath("$.data.totalElements").value(2))
|
.andExpect(jsonPath("$.data.totalElements").value(2))
|
||||||
@@ -222,6 +225,8 @@ class NoticeConfigControllerTest {
|
|||||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receiver/{id}", 7565463543L))
|
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receiver/{id}", 7565463543L))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||||
|
.andExpect(jsonPath("$.data.accessToken").value(NoticeReceiverMaskUtil.SECRET_MASK + "739d"))
|
||||||
|
.andExpect(jsonPath("$.data.email").value("2762242004@qq.com"))
|
||||||
.andReturn();
|
.andReturn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
@@ -22,6 +22,7 @@ import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
|
|||||||
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
|
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
|
||||||
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
|
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
|
||||||
import org.apache.hertzbeat.alert.service.impl.NoticeConfigServiceImpl;
|
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.cache.CacheFactory;
|
||||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||||
@@ -48,6 +49,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
@@ -216,6 +218,39 @@ class NoticeConfigServiceTest {
|
|||||||
verify(noticeReceiverDao, times(1)).save(noticeReceiver);
|
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
|
@Test
|
||||||
void deleteReceiver() {
|
void deleteReceiver() {
|
||||||
final Long receiverId = 23342525L;
|
final Long receiverId = 23342525L;
|
||||||
|
|||||||
+134
@@ -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("tom@usthe.com");
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -48,7 +48,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
|||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hzb_notice_receiver")
|
@Table(name = "hzb_notice_receiver")
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder(toBuilder = true)
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Schema(description = "Message notification recipient entity")
|
@Schema(description = "Message notification recipient entity")
|
||||||
|
|||||||
Reference in New Issue
Block a user