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
Li 2023-05-26 22:52:21 +08:00 committed by GitHub
parent da5fb1a252
commit f5493a6d86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 148 additions and 4 deletions

View File

@ -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();
return Mono.just(CommentVo.from(comment))
.flatMap(commentVo -> populateStats(Comment.class, commentVo)
@ -116,7 +116,26 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
.flatMap(commentVo -> getOwnerInfo(owner)
.doOnNext(commentVo::setOwner)
.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>
@ -130,7 +149,7 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
.defaultIfEmpty(CommentStatsVo.empty());
}
private Mono<ReplyVo> toReplyVo(Reply reply) {
Mono<ReplyVo> toReplyVo(Reply reply) {
return Mono.just(ReplyVo.from(reply))
.flatMap(replyVo -> populateStats(Reply.class, replyVo)
.doOnNext(replyVo::setStats)
@ -138,7 +157,26 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService
.flatMap(replyVo -> getOwnerInfo(reply.getSpec().getOwner())
.doOnNext(replyVo::setOwner)
.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) {

View File

@ -6,10 +6,12 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@ -17,6 +19,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit.jupiter.SpringExtension;
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.Ref;
import run.halo.app.infra.AnonymousUserConst;
import run.halo.app.infra.utils.JsonUtils;
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");
}
@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 comment = new Comment();
comment.setMetadata(new Metadata());
@ -312,6 +367,57 @@ class CommentPublicQueryServiceImplTest {
.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")
private void mockWhenListRely() {
// Mock