feat: 接入消息加密与实名认证能力
This commit is contained in:
+138
-24
@@ -226,6 +226,24 @@ func (s testThirdPartyConfigStore) Get(_ string, keys ...string) (map[string]str
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type testRealNameConfigStore struct {
|
||||
policy *types.RealNamePolicy
|
||||
err error
|
||||
}
|
||||
|
||||
func (s testRealNameConfigStore) Policy(tenantID types.TenantID) (*types.RealNamePolicy, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
if s.policy == nil {
|
||||
return &types.RealNamePolicy{TenantID: tenantID, ReviewPolicy: types.RealNameReviewPolicyNone, ProviderMode: "id_card_2factor", AllowSkip: true, RemindAfter: "24h"}, nil
|
||||
}
|
||||
policy := *s.policy
|
||||
policy.TenantID = tenantID
|
||||
policy.Normalize()
|
||||
return &policy, nil
|
||||
}
|
||||
|
||||
type testConversationsStore struct {
|
||||
topic string
|
||||
settings *types.ConversationSettings
|
||||
@@ -2096,6 +2114,48 @@ func TestServeIMRealNameSubmissionCreatesPending(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMRealNameSubmissionAutoReviewFallsBackToManual(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
expectIMBearer(t, ctrl, "token-1", 7, 42)
|
||||
oldSecurity := store.Security
|
||||
oldRealNameConfig := store.RealNameConfig
|
||||
oldThirdParty := store.ThirdPartyConfig
|
||||
security := &testSecurityStore{}
|
||||
store.Security = security
|
||||
store.RealNameConfig = testRealNameConfigStore{policy: &types.RealNamePolicy{
|
||||
ReviewPolicy: types.RealNameReviewPolicyAutoManual,
|
||||
ProviderMode: "id_card_2factor",
|
||||
AllowSkip: true,
|
||||
RemindAfter: "24h",
|
||||
}}
|
||||
store.ThirdPartyConfig = testThirdPartyConfigStore{}
|
||||
defer func() {
|
||||
store.Security = oldSecurity
|
||||
store.RealNameConfig = oldRealNameConfig
|
||||
store.ThirdPartyConfig = oldThirdParty
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/security/realname/submissions",
|
||||
bytes.NewBufferString(`{"name":"张三","id_type":"cn_id","id_number":"110101199003071234"}`))
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
req.Header.Set("Idempotency-Key", "idem-1")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMRealNameSubmissions(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||
}
|
||||
if security.realName == nil || security.realName.Status != "manual_review" || security.realName.ReviewReason == nil {
|
||||
t.Fatalf("unexpected stored realname: %#v", security.realName)
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
if data["status"] != "pending" || data["review_status"] != "manual_review" {
|
||||
t.Fatalf("unexpected response: %#v", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMRealNameAppealCreatesPending(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -5087,6 +5147,7 @@ func TestServeIMFavoriteForwardSavesMessages(t *testing.T) {
|
||||
tokenAuth.EXPECT().IsInitialized().Return(true)
|
||||
tokenAuth.EXPECT().Authenticate(auth.AuthContext{}, []byte("token-1")).
|
||||
Return(&auth.Rec{TenantID: 7, Uid: 42, AuthLevel: auth.LevelAuth}, nil, nil)
|
||||
mockStore.EXPECT().GetUidString().Return("fav-mid")
|
||||
mockStore.EXPECT().GetUidString().Return("comment-mid")
|
||||
store.Store = mockStore
|
||||
oldFavorites := store.Favorites
|
||||
@@ -5137,6 +5198,9 @@ func TestServeIMFavoriteForwardSavesMessages(t *testing.T) {
|
||||
if msg.Head["x-im-forwarded"] != true {
|
||||
t.Fatalf("forward marker not set: %#v", msg.Head)
|
||||
}
|
||||
if msg.Head["x-im-client-mid"] != "fwd_fav-mid" {
|
||||
t.Fatalf("forward client mid not set: %#v", msg.Head)
|
||||
}
|
||||
content := msg.Content.(map[string]any)
|
||||
if content["text"] != "hello" || content["comment"] != nil || content["snapshot"] != nil {
|
||||
t.Fatalf("unexpected forward content: %#v", content)
|
||||
@@ -5153,11 +5217,82 @@ func TestServeIMFavoriteForwardSavesMessages(t *testing.T) {
|
||||
t.Fatalf("forward status: got %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
items := decodeIMHTTPResponse(t, rec).Data.(map[string]any)["items"].([]any)
|
||||
if got := items[0].(map[string]any); got["status"] != "sent" || got["seq"].(float64) != 11 || got["comment_seq"].(float64) != 10 {
|
||||
if got := items[0].(map[string]any); got["status"] != "sent" || got["seq"].(float64) != 11 || got["comment_seq"].(float64) != 10 || got["client_mid"] != "fwd_fav-mid" {
|
||||
t.Fatalf("unexpected forward response: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMFavoriteForwardMediaUsesNewClientMid(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
oldStore := store.Store
|
||||
mockStore := mock_store.NewMockPersistentStorageInterface(ctrl)
|
||||
tokenAuth := mock_auth.NewMockAuthHandler(ctrl)
|
||||
mockStore.EXPECT().GetLogicalAuthHandler("token").Return(tokenAuth)
|
||||
tokenAuth.EXPECT().IsInitialized().Return(true)
|
||||
tokenAuth.EXPECT().Authenticate(auth.AuthContext{}, []byte("token-1")).
|
||||
Return(&auth.Rec{TenantID: 7, Uid: 42, AuthLevel: auth.LevelAuth}, nil, nil)
|
||||
mockStore.EXPECT().GetUidString().Return("media-mid")
|
||||
store.Store = mockStore
|
||||
oldFavorites := store.Favorites
|
||||
oldSubs := store.Subs
|
||||
oldMessages := store.Messages
|
||||
now := time.Date(2026, 8, 13, 9, 0, 0, 0, time.UTC)
|
||||
store.Favorites = &testFavoritesStore{items: []types.FavoriteItem{{
|
||||
TenantID: 7,
|
||||
FavoriteID: "fav_img",
|
||||
UserID: 42,
|
||||
ItemType: "image",
|
||||
SourceTopic: "grpAlpha",
|
||||
SourceSeq: 17,
|
||||
Snapshot: map[string]any{
|
||||
"head": map[string]any{"type": "image", "x-im-type": "image", "x-im-client-mid": "origin-mid"},
|
||||
"content": map[string]any{"file_id": "file-img", "mime": "image/png"},
|
||||
},
|
||||
Availability: "available",
|
||||
CollectedAt: now,
|
||||
}}}
|
||||
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
||||
messages := mock_store.NewMockMessagesPersistenceInterface(ctrl)
|
||||
store.Subs = subs
|
||||
store.Messages = messages
|
||||
defer func() {
|
||||
store.Store = oldStore
|
||||
store.Favorites = oldFavorites
|
||||
store.Subs = oldSubs
|
||||
store.Messages = oldMessages
|
||||
}()
|
||||
subs.EXPECT().Get(types.TenantID(7), "grpBeta", types.Uid(42), false).Return(&types.Subscription{
|
||||
TenantID: 7, User: types.Uid(42).String(), Topic: "grpBeta", ModeWant: types.ModeCPublic, ModeGiven: types.ModeCPublic,
|
||||
}, nil)
|
||||
messages.EXPECT().Save(types.TenantID(7), gomock.Any(), gomock.Nil(), true).DoAndReturn(
|
||||
func(_ types.TenantID, msg *types.Message, _ []string, _ bool) (*types.MessageSaveResult, error) {
|
||||
if msg.Topic != "grpBeta" || msg.Head["x-im-type"] != "image" {
|
||||
t.Fatalf("unexpected media forward message: %#v", msg)
|
||||
}
|
||||
if msg.Head["x-im-client-mid"] != "fwd_media-mid" {
|
||||
t.Fatalf("media forward reused original client mid: %#v", msg.Head)
|
||||
}
|
||||
content := msg.Content.(map[string]any)
|
||||
if content["file_id"] != "file-img" || content["mime"] != "image/png" {
|
||||
t.Fatalf("unexpected media forward content: %#v", content)
|
||||
}
|
||||
return &types.MessageSaveResult{SeqId: 22, MarkedReadBySender: true}, nil
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/favorites/forward", bytes.NewBufferString(`{"favorite_ids":["fav_img"],"target_topics":["grpBeta"]}`))
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
rec := httptest.NewRecorder()
|
||||
serveIMFavoriteResource(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("forward status: got %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
items := decodeIMHTTPResponse(t, rec).Data.(map[string]any)["items"].([]any)
|
||||
if got := items[0].(map[string]any); got["status"] != "sent" || got["seq"].(float64) != 22 || got["client_mid"] != "fwd_media-mid" {
|
||||
t.Fatalf("unexpected media forward response: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMFavoriteForwardRejectsUnauthorizedTarget(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -5511,7 +5646,7 @@ func TestServeIMMessageForwardKafkaAssignsSeqAndBroadcasts(t *testing.T) {
|
||||
t.Fatalf("unexpected forward response: %#v", data)
|
||||
}
|
||||
got := items[0].(map[string]any)
|
||||
if got["target_topic"] != targetTopic || got["seq"].(float64) != 35 {
|
||||
if got["target_topic"] != targetTopic || got["seq"].(float64) != 35 || got["status"] != "pending" || got["reason"] != "MESSAGE_PERSIST_PENDING" {
|
||||
t.Fatalf("unexpected forward item: %#v", got)
|
||||
}
|
||||
if failed := data["failed"].([]any); len(failed) != 0 {
|
||||
@@ -5525,29 +5660,8 @@ func TestServeIMMessageForwardKafkaAssignsSeqAndBroadcasts(t *testing.T) {
|
||||
}
|
||||
select {
|
||||
case delivered := <-globals.hub.routeSrv:
|
||||
if delivered.RcptTo != targetTopic || delivered.Data == nil || delivered.Data.SeqId != 35 {
|
||||
t.Fatalf("unexpected broadcast message: %#v", delivered)
|
||||
}
|
||||
t.Fatalf("pending forward should not broadcast immediately: %#v", delivered)
|
||||
default:
|
||||
t.Fatal("expected forwarded message to be broadcast")
|
||||
}
|
||||
presByUser := make(map[string]*MsgServerPres)
|
||||
for range 2 {
|
||||
select {
|
||||
case delivered := <-globals.hub.routeSrv:
|
||||
if delivered.Pres == nil {
|
||||
t.Fatalf("unexpected non-presence message: %#v", delivered)
|
||||
}
|
||||
presByUser[delivered.RcptTo] = delivered.Pres
|
||||
default:
|
||||
t.Fatal("expected forwarded message presence notifications")
|
||||
}
|
||||
}
|
||||
if pres := presByUser[types.Uid(42).UserId()]; pres == nil || pres.Topic != "me" || pres.What != "msg" || pres.Src != targetUser.UserId() || pres.SeqId != 35 || pres.AcsActor != "" || pres.SkipTopic != targetTopic {
|
||||
t.Fatalf("unexpected sender presence: %#v", pres)
|
||||
}
|
||||
if pres := presByUser[targetUser.UserId()]; pres == nil || pres.Topic != "me" || pres.What != "msg" || pres.Src != types.Uid(42).UserId() || pres.SeqId != 35 || pres.AcsActor != types.Uid(42).UserId() || pres.SkipTopic != targetTopic {
|
||||
t.Fatalf("unexpected receiver presence: %#v", pres)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user