- 新增 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>
148 lines
6.0 KiB
Go
148 lines
6.0 KiB
Go
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)
|
||
// 调 service,starID 是 200 而非硬编码 0;friendUserID 取自 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
|
||
// 里伪造别人的 ID,provider 必须忽略 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 无可信身份 → Unauthenticated,service 不被调用。
|
||
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")
|
||
}
|