mirror of https://github.com/halo-dev/halo
perf: data desensitization for comments and replies (#3936)
#### What type of PR is this? /kind improvement /area core /milestone 2.6.x #### What this PR does / why we need it: 对客户端评论接口进行脱敏处理,移除 `ipAddress` 属性以及 owner 下的 `email` 及 `name` 属性。 UA 由于主题端有使用的可能以及敏感性不强,因此未移除。 对于 #3915 中提到的评论时间为排序时间,需要在 [`https://github.com/halo-sigs/plugin-comment-widget`](https://github.com/halo-sigs/plugin-comment-widget) 插件中做处理。 #### Which issue(s) this PR fixes: #3915 #### Special notes for your reviewer: 查看评论接口 `/apis/api.halo.run/v1alpha1/comments` 及回复接口 `/apis/api.halo.run/v1alpha1/comments/{commentName}/reply` 返回字段是否存在 `spec.ipAddress` 、`owner.email`与 `owner.name` 字段。 #### Does this PR introduce a user-facing change? ```release-note 对客户端评论及回复列表接口进行脱敏处理 ```pull/4005/head
parent
da5fb1a252
commit
f5493a6d86
|
@ -107,7 +107,7 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<CommentVo> toCommentVo(Comment comment) {
|
Mono<CommentVo> toCommentVo(Comment comment) {
|
||||||
Comment.CommentOwner owner = comment.getSpec().getOwner();
|
Comment.CommentOwner owner = comment.getSpec().getOwner();
|
||||||
return Mono.just(CommentVo.from(comment))
|
return Mono.just(CommentVo.from(comment))
|
||||||
.flatMap(commentVo -> populateStats(Comment.class, commentVo)
|
.flatMap(commentVo -> populateStats(Comment.class, commentVo)
|
||||||
|
@ -116,7 +116,26 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
|
||||||
.flatMap(commentVo -> getOwnerInfo(owner)
|
.flatMap(commentVo -> getOwnerInfo(owner)
|
||||||
.doOnNext(commentVo::setOwner)
|
.doOnNext(commentVo::setOwner)
|
||||||
.thenReturn(commentVo)
|
.thenReturn(commentVo)
|
||||||
);
|
)
|
||||||
|
.flatMap(commentVo -> filterCommentSensitiveData(commentVo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<? extends CommentVo> filterCommentSensitiveData(CommentVo commentVo) {
|
||||||
|
var owner = commentVo.getOwner();
|
||||||
|
commentVo.setOwner(OwnerInfo
|
||||||
|
.builder()
|
||||||
|
.displayName(owner.getDisplayName())
|
||||||
|
.avatar(owner.getAvatar())
|
||||||
|
.kind(owner.getKind())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
commentVo.getSpec().setIpAddress("");
|
||||||
|
var specOwner = commentVo.getSpec().getOwner();
|
||||||
|
specOwner.setName("");
|
||||||
|
if (specOwner.getAnnotations() != null) {
|
||||||
|
specOwner.getAnnotations().remove("Email");
|
||||||
|
}
|
||||||
|
return Mono.just(commentVo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <E extends AbstractExtension, T extends ExtensionVoOperator> Mono<CommentStatsVo>
|
private <E extends AbstractExtension, T extends ExtensionVoOperator> Mono<CommentStatsVo>
|
||||||
|
@ -130,7 +149,7 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
|
||||||
.defaultIfEmpty(CommentStatsVo.empty());
|
.defaultIfEmpty(CommentStatsVo.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<ReplyVo> toReplyVo(Reply reply) {
|
Mono<ReplyVo> toReplyVo(Reply reply) {
|
||||||
return Mono.just(ReplyVo.from(reply))
|
return Mono.just(ReplyVo.from(reply))
|
||||||
.flatMap(replyVo -> populateStats(Reply.class, replyVo)
|
.flatMap(replyVo -> populateStats(Reply.class, replyVo)
|
||||||
.doOnNext(replyVo::setStats)
|
.doOnNext(replyVo::setStats)
|
||||||
|
@ -138,7 +157,26 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
|
||||||
.flatMap(replyVo -> getOwnerInfo(reply.getSpec().getOwner())
|
.flatMap(replyVo -> getOwnerInfo(reply.getSpec().getOwner())
|
||||||
.doOnNext(replyVo::setOwner)
|
.doOnNext(replyVo::setOwner)
|
||||||
.thenReturn(replyVo)
|
.thenReturn(replyVo)
|
||||||
);
|
)
|
||||||
|
.flatMap(replyVo -> filterReplySensitiveData(replyVo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<? extends ReplyVo> filterReplySensitiveData(ReplyVo replyVo) {
|
||||||
|
var owner = replyVo.getOwner();
|
||||||
|
replyVo.setOwner(OwnerInfo
|
||||||
|
.builder()
|
||||||
|
.displayName(owner.getDisplayName())
|
||||||
|
.avatar(owner.getAvatar())
|
||||||
|
.kind(owner.getKind())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
replyVo.getSpec().setIpAddress("");
|
||||||
|
var specOwner = replyVo.getSpec().getOwner();
|
||||||
|
specOwner.setName("");
|
||||||
|
if (specOwner.getAnnotations() != null) {
|
||||||
|
specOwner.getAnnotations().remove("Email");
|
||||||
|
}
|
||||||
|
return Mono.just(replyVo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<OwnerInfo> getOwnerInfo(Comment.CommentOwner owner) {
|
private Mono<OwnerInfo> getOwnerInfo(Comment.CommentOwner owner) {
|
||||||
|
|
|
@ -6,10 +6,12 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import org.json.JSONException;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -17,6 +19,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
import org.skyscreamer.jsonassert.JSONAssert;
|
||||||
import org.springframework.security.test.context.support.WithMockUser;
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -34,6 +37,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.Ref;
|
import run.halo.app.extension.Ref;
|
||||||
import run.halo.app.infra.AnonymousUserConst;
|
import run.halo.app.infra.AnonymousUserConst;
|
||||||
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
import run.halo.app.metrics.CounterService;
|
import run.halo.app.metrics.CounterService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,6 +167,57 @@ class CommentPublicQueryServiceImplTest {
|
||||||
assertThat(result).isEqualTo("1, 2, 3, 4, 5, 6, 9, 14, 10, 8, 7, 13, 12, 11");
|
assertThat(result).isEqualTo("1, 2, 3, 4, 5, 6, 9, 14, 10, 8, 7, 13, 12, 11");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void desensitizeComment() throws JSONException {
|
||||||
|
var commentOwner = new Comment.CommentOwner();
|
||||||
|
commentOwner.setName("fake-user");
|
||||||
|
commentOwner.setDisplayName("Fake User");
|
||||||
|
commentOwner.setAnnotations(new HashMap<>() {
|
||||||
|
{
|
||||||
|
put(Comment.CommentOwner.KIND_EMAIL, "mail@halo.run");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var comment = commentForCompare("1", null, true, 0);
|
||||||
|
comment.getSpec().setIpAddress("127.0.0.1");
|
||||||
|
comment.getSpec().setOwner(commentOwner);
|
||||||
|
|
||||||
|
Counter counter = new Counter();
|
||||||
|
counter.setUpvote(0);
|
||||||
|
when(counterService.getByName(any())).thenReturn(Mono.just(counter));
|
||||||
|
|
||||||
|
var result = commentPublicQueryService.toCommentVo(comment).block();
|
||||||
|
result.getMetadata().setCreationTimestamp(null);
|
||||||
|
result.getSpec().setCreationTime(null);
|
||||||
|
JSONAssert.assertEquals("""
|
||||||
|
{
|
||||||
|
"metadata":{
|
||||||
|
"name":"1"
|
||||||
|
},
|
||||||
|
"spec":{
|
||||||
|
"owner":{
|
||||||
|
"name":"",
|
||||||
|
"displayName":"Fake User",
|
||||||
|
"annotations":{
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ipAddress":"",
|
||||||
|
"priority":0,
|
||||||
|
"top":true
|
||||||
|
},
|
||||||
|
"owner":{
|
||||||
|
"kind":"User",
|
||||||
|
"displayName":"fake-display-name"
|
||||||
|
},
|
||||||
|
"stats":{
|
||||||
|
"upvote":0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
JsonUtils.objectToJson(result),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
Comment commentForCompare(String name, Instant creationTime, boolean top, int priority) {
|
Comment commentForCompare(String name, Instant creationTime, boolean top, int priority) {
|
||||||
Comment comment = new Comment();
|
Comment comment = new Comment();
|
||||||
comment.setMetadata(new Metadata());
|
comment.setMetadata(new Metadata());
|
||||||
|
@ -312,6 +367,57 @@ class CommentPublicQueryServiceImplTest {
|
||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void desensitizeReply() throws JSONException {
|
||||||
|
var reply = createReply();
|
||||||
|
reply.getSpec().getOwner()
|
||||||
|
.setAnnotations(new HashMap<>() {
|
||||||
|
{
|
||||||
|
put(Comment.CommentOwner.KIND_EMAIL, "mail@halo.run");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reply.getSpec().setIpAddress("127.0.0.1");
|
||||||
|
|
||||||
|
Counter counter = new Counter();
|
||||||
|
counter.setUpvote(0);
|
||||||
|
when(counterService.getByName(any())).thenReturn(Mono.just(counter));
|
||||||
|
|
||||||
|
var result = commentPublicQueryService.toReplyVo(reply).block();
|
||||||
|
result.getMetadata().setCreationTimestamp(null);
|
||||||
|
result.getSpec().setCreationTime(null);
|
||||||
|
JSONAssert.assertEquals("""
|
||||||
|
{
|
||||||
|
"metadata":{
|
||||||
|
"name":"fake-reply"
|
||||||
|
},
|
||||||
|
"spec":{
|
||||||
|
"raw":"fake-raw",
|
||||||
|
"content":"fake-content",
|
||||||
|
"owner":{
|
||||||
|
"kind":"User",
|
||||||
|
"name":"",
|
||||||
|
"displayName":"fake-display-name",
|
||||||
|
"annotations":{
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ipAddress":"",
|
||||||
|
"hidden":false,
|
||||||
|
"commentName":"fake-comment"
|
||||||
|
},
|
||||||
|
"owner":{
|
||||||
|
"kind":"User",
|
||||||
|
"displayName":"fake-display-name"
|
||||||
|
},
|
||||||
|
"stats":{
|
||||||
|
"upvote":0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
JsonUtils.objectToJson(result),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void mockWhenListRely() {
|
private void mockWhenListRely() {
|
||||||
// Mock
|
// Mock
|
||||||
|
|
Loading…
Reference in New Issue