topfans/backend/services/socialService/provider/social_provider_test.go
zerosaturation b7022d2dc1 fix(backend): auth boundary — trusted identity from ctx (batch 2)
- 新增 pkg/authctx: 从 Dubbo attachment/gRPC metadata 提取可信 user_id/star_id,
  统一覆盖 req 同名字段, 缺身份返 Unauthenticated。
- 各 provider 接入(堵身份伪造/越权):
  * moderation SubmitReport 等 6 RPC(举报人伪造)
  * asset CheckAssetLike/GetAssetQrcode/TrackShare(点赞/分享归因伪造)
  * social CheckFriendship(修 starID=0 隐私预言机)
  * activity PurchaseItem/BatchPurchaseItem(水晶扣费伪造)等 5 RPC
  * gallery/aiChat/task/notification 迁移 authctx, 删散落 extractUserInfo*
- social 正确性: GetUserLikedAssets OR 显式分组(防御); GetRandomUsersByStar 真随机(去 rand.Seed)。
- gateway: /auth/validate 移入 AuthMiddleware 保护组(/refresh 保留,依赖注入身份)。
- 删 userService 已迁移死函数; notification 缺身份错误码统一为 Unauthenticated。
- 各 provider 单测(伪造身份被覆盖 + 缺身份拒绝)。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-23 01:12:35 +08:00

148 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package provider
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/topfans/backend/pkg/authctx"
"github.com/topfans/backend/pkg/logger"
pb "github.com/topfans/backend/pkg/proto/social"
"go.uber.org/zap"
)
// 测试环境初始化全局 logger否则 provider 里的 logger.Logger.Debug 会 nil panic
func init() {
logger.Logger = zap.NewNop()
}
// ========== mock FriendService ==========
//
// mockFriendSvc 实现 service.FriendService 接口,只捕获 CheckFriendship 的
// 入参(验证 provider 是否把 ctx 里的可信身份 userID/starID 传给 service
// 而不是信任 req.UserId / 硬编码 starID=0。其余方法是编译占位。
type mockFriendSvc struct {
called bool
gotUserID int64
gotStarID int64
gotFriendUserID int64
checkFriendResp *pb.CheckFriendshipResponse
checkFriendErr error
}
func (m *mockFriendSvc) CheckFriendship(ctx context.Context, userID, starID, friendUserID int64) (*pb.CheckFriendshipResponse, error) {
m.called = true
m.gotUserID = userID
m.gotStarID = starID
m.gotFriendUserID = friendUserID
if m.checkFriendResp == nil && m.checkFriendErr == nil {
return &pb.CheckFriendshipResponse{}, nil
}
return m.checkFriendResp, m.checkFriendErr
}
// ---- 以下为编译占位CheckFriendship 测试用不到 ----
func (m *mockFriendSvc) SendFriendRequest(ctx context.Context, req *pb.SendFriendRequestRequest, userID, starID int64) (*pb.SendFriendRequestResponse, error) {
return nil, nil
}
func (m *mockFriendSvc) GetFriendRequests(ctx context.Context, req *pb.GetFriendRequestsRequest, userID, starID int64) (*pb.GetFriendRequestsResponse, error) {
return nil, nil
}
func (m *mockFriendSvc) HandleFriendRequest(ctx context.Context, req *pb.HandleFriendRequestRequest, userID, starID int64) (*pb.HandleFriendRequestResponse, error) {
return nil, nil
}
func (m *mockFriendSvc) GetFriendList(ctx context.Context, req *pb.GetFriendListRequest, userID, starID int64) (*pb.GetFriendListResponse, error) {
return nil, nil
}
func (m *mockFriendSvc) DeleteFriend(ctx context.Context, req *pb.DeleteFriendRequest, userID, starID int64) (*pb.DeleteFriendResponse, error) {
return nil, nil
}
func (m *mockFriendSvc) SetFriendRemark(ctx context.Context, req *pb.SetFriendRemarkRequest, userID, starID int64) (*pb.SetFriendRemarkResponse, error) {
return nil, nil
}
func (m *mockFriendSvc) GetFriendCount(ctx context.Context, req *pb.GetFriendCountRequest, userID, starID int64) (*pb.GetFriendCountResponse, error) {
return nil, nil
}
func (m *mockFriendSvc) SearchUserForFriend(ctx context.Context, req *pb.SearchUserForFriendRequest, userID, starID int64) (*pb.SearchUserForFriendResponse, error) {
return nil, nil
}
func (m *mockFriendSvc) GetRandomUsers(ctx context.Context, req *pb.GetRandomUsersRequest, userID, starID int64) (*pb.GetRandomUsersResponse, error) {
return nil, nil
}
func (m *mockFriendSvc) GetUsersPaged(ctx context.Context, req *pb.GetUsersPagedRequest, userID, starID int64) (*pb.GetUsersPagedResponse, error) {
return nil, nil
}
// ========== 测试 ==========
// TestCheckFriendship_UsesCtxStarID 验证provider 用 ctx 里的可信 (uid=100, sid=200)
// 调 servicestarID 是 200 而非硬编码 0friendUserID 取自 req.FriendUserId。
func TestCheckFriendship_UsesCtxStarID(t *testing.T) {
mockSvc := &mockFriendSvc{}
p := NewSocialProvider(mockSvc, nil)
ctx := authctx.WithIdentity(context.Background(), 100, 200)
req := &pb.CheckFriendshipRequest{UserId: 0, FriendUserId: 200}
_, err := p.CheckFriendship(ctx, req)
require.NoError(t, err)
require.True(t, mockSvc.called, "service.CheckFriendship should be called")
require.Equal(t, int64(100), mockSvc.gotUserID, "userID must come from ctx")
require.Equal(t, int64(200), mockSvc.gotStarID, "starID must be ctx sid (200), NOT hardcoded 0")
require.NotEqual(t, int64(0), mockSvc.gotStarID, "starID must not be the old hardcoded 0")
require.Equal(t, int64(200), mockSvc.gotFriendUserID, "friendUserID from req")
}
// TestCheckFriendship_IgnoresForgedUserId 验证隐私预言机修复:调用方在 req.UserId
// 里伪造别人的 IDprovider 必须忽略 req.UserId改用 ctx 里的可信 uid。
// 这样调用方只能查询"我和某人是否好友",不能探测任意两人的好友关系。
func TestCheckFriendship_IgnoresForgedUserId(t *testing.T) {
mockSvc := &mockFriendSvc{}
p := NewSocialProvider(mockSvc, nil)
ctx := authctx.WithIdentity(context.Background(), 100, 200)
// 攻击者把 UserId 写成 999想探测 999 和 555 是否好友)
req := &pb.CheckFriendshipRequest{UserId: 999, FriendUserId: 555}
_, err := p.CheckFriendship(ctx, req)
require.NoError(t, err)
require.Equal(t, int64(100), mockSvc.gotUserID, "forged req.UserId=999 must be ignored, use ctx uid=100")
}
// TestCheckFriendship_NoIdentity 验证ctx 无可信身份 → Unauthenticatedservice 不被调用。
func TestCheckFriendship_NoIdentity(t *testing.T) {
mockSvc := &mockFriendSvc{}
p := NewSocialProvider(mockSvc, nil)
ctx := context.Background() // 无身份
req := &pb.CheckFriendshipRequest{UserId: 100, FriendUserId: 200}
_, err := p.CheckFriendship(ctx, req)
require.Error(t, err)
st, ok := status.FromError(err)
require.True(t, ok, "error should be a grpc status")
require.Equal(t, codes.Unauthenticated, st.Code())
require.False(t, mockSvc.called, "service must NOT be called when identity is missing")
}
// TestCheckFriendship_MissingFriendUserID 验证friend_user_id 缺失 → InvalidArgument。
func TestCheckFriendship_MissingFriendUserID(t *testing.T) {
mockSvc := &mockFriendSvc{}
p := NewSocialProvider(mockSvc, nil)
ctx := authctx.WithIdentity(context.Background(), 100, 200)
req := &pb.CheckFriendshipRequest{UserId: 100, FriendUserId: 0}
_, err := p.CheckFriendship(ctx, req)
require.Error(t, err)
st, ok := status.FromError(err)
require.True(t, ok)
require.Equal(t, codes.InvalidArgument, st.Code())
require.False(t, mockSvc.called, "service must NOT be called when friend_user_id missing")
}