diff --git a/internal/rpc/auth_gate.go b/internal/rpc/auth_gate.go index 4beaf504..1f69feb7 100644 --- a/internal/rpc/auth_gate.go +++ b/internal/rpc/auth_gate.go @@ -39,6 +39,7 @@ func rpcAllowedWithoutAuthorization(id uint32) bool { tg.HelpGetConfigRequestTypeID, tg.HelpGetNearestDCRequestTypeID, tg.HelpGetInviteTextRequestTypeID, + tg.HelpSaveAppLogRequestTypeID, tg.HelpGetAppConfigRequestTypeID, tg.HelpGetCountriesListRequestTypeID, tg.HelpGetTimezonesListRequestTypeID, diff --git a/internal/rpc/help.go b/internal/rpc/help.go index eced459d..341b5b48 100644 --- a/internal/rpc/help.go +++ b/internal/rpc/help.go @@ -23,6 +23,9 @@ func (r *Router) registerHelp(d *tlprofile.Dispatcher) { registerRPC[*tg.HelpGetInviteTextRequest](d, tlprofile.SemanticMethodHelpGetInviteText, func(ctx context.Context, layerRequest *tg.HelpGetInviteTextRequest) (any, error) { return &tg.HelpInviteText{Message: "Join me on Telegram."}, nil }) + registerRPC[*tg.HelpSaveAppLogRequest](d, tlprofile.SemanticMethodHelpSaveAppLog, func(ctx context.Context, _ *tg.HelpSaveAppLogRequest) (any, error) { + return r.onHelpSaveAppLog(ctx) + }) registerRPC[*tg.HelpGetAppUpdateRequest](d, tlprofile.SemanticMethodHelpGetAppUpdate, func(ctx context.Context, layerRequest *tg.HelpGetAppUpdateRequest) (any, error) { source := layerRequest. Source @@ -113,6 +116,21 @@ func (r *Router) registerHelp(d *tlprofile.Dispatcher) { }) } +// onHelpSaveAppLog 为官方客户端的 fire-and-forget 应用遥测提供有界兼容应答。 +// telesrv 当前不运营遥测产品,因此不读取、记录或持久化事件内容;请求已在 exact +// Layer admission 处受 wire/vector/aggregate/depth 限制。该方法按 TL 访问约束允许 +// 未授权连接调用,但已登录 bot 必须拒绝。 +func (r *Router) onHelpSaveAppLog(ctx context.Context) (bool, error) { + userID, authorized, err := r.currentUserID(ctx) + if err != nil { + return false, internalErr() + } + if authorized && r.userIsBot(ctx, userID) { + return false, botMethodInvalidErr() + } + return true, nil +} + func (r *Router) onHelpGetConfig(ctx context.Context) (*tg.Config, error) { config := tdesktop.BuildConfig(r.cfg.DC, r.cfg.IP, r.cfg.Port, r.clock.Now(), r.cfg.PublicBaseURL) userID, authorized, err := r.currentUserID(ctx) diff --git a/internal/rpc/help_save_app_log_test.go b/internal/rpc/help_save_app_log_test.go new file mode 100644 index 00000000..c58f12e7 --- /dev/null +++ b/internal/rpc/help_save_app_log_test.go @@ -0,0 +1,74 @@ +package rpc + +import ( + "context" + "fmt" + "testing" + + "github.com/iamxvbaba/td/clock" + "github.com/iamxvbaba/td/tg" + "github.com/iamxvbaba/td/tgerr" + "github.com/iamxvbaba/td/tlprofile" + "go.uber.org/zap/zaptest" + + appusers "telesrv/internal/app/users" + "telesrv/internal/domain" + "telesrv/internal/store/memory" +) + +func TestHelpSaveAppLogCompatibilityAckAcrossExactProfiles(t *testing.T) { + r := New(Config{}, Deps{Auth: &captureAuthService{}}, zaptest.NewLogger(t), clock.System) + requests := map[string]*tg.HelpSaveAppLogRequest{ + "empty": {}, + "android_device_stat": { + Events: []tg.InputAppEvent{{ + Time: 1_721_234_567.25, + Type: "android_sdcard_exists", + Peer: 1, + Data: &tg.JSONBool{Value: true}, + }}, + }, + } + contexts := map[string]context.Context{ + "unauthenticated": context.Background(), + "user": WithUserID(context.Background(), 42), + } + + for profile := tlprofile.Profile225; profile <= tlprofile.Profile228; profile++ { + for contextName, ctx := range contexts { + for requestName, req := range requests { + name := fmt.Sprintf("layer_%d/%s/%s", profile, contextName, requestName) + t.Run(name, func(t *testing.T) { + for attempt := 1; attempt <= 2; attempt++ { + result, method := dispatchExactLayerRPCTest(t, r, ctx, profile, req) + if method != "help.saveAppLog" { + t.Fatalf("attempt %d method = %q, want help.saveAppLog", attempt, method) + } + if value, ok := dispatchCanonicalValue(result).(bool); !ok || !value { + t.Fatalf("attempt %d response = %#v (%T), want true", attempt, dispatchCanonicalValue(result), result) + } + } + }) + } + } + } +} + +func TestHelpSaveAppLogRejectsBot(t *testing.T) { + ctx := context.Background() + users := memory.NewUserStore() + bot, err := users.Create(ctx, domain.User{ + Phone: "+10000000001", + FirstName: "TelemetryBot", + AccessHash: 101, + Bot: true, + }) + if err != nil { + t.Fatal(err) + } + r := New(Config{}, Deps{Users: appusers.NewService(users)}, zaptest.NewLogger(t), clock.System) + + if _, err := r.onHelpSaveAppLog(WithUserID(ctx, bot.ID)); !tgerr.Is(err, "BOT_METHOD_INVALID") { + t.Fatalf("bot saveAppLog err = %v, want BOT_METHOD_INVALID", err) + } +}