From 93717394b22b4ad2bd222e478733de2ef5ef7d74 Mon Sep 17 00:00:00 2001 From: Jlypx Date: Sun, 19 Jul 2026 21:41:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=BF=81=E7=A7=BB=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=20IP=20=E5=85=BC=E5=AE=B9=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- backend/internal/service/domain_constants.go | 1 + backend/internal/service/setting_parse.go | 5 +- backend/internal/service/setting_service.go | 36 +++- .../service/setting_service_update_test.go | 164 ++++++++++++++++++ 4 files changed, 196 insertions(+), 10 deletions(-) diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go index cbbb4b7850..c99d751c87 100644 --- a/backend/internal/service/domain_constants.go +++ b/backend/internal/service/domain_constants.go @@ -165,6 +165,7 @@ const ( // API Key IP 访问控制设置 SettingKeyAPIKeyACLTrustForwardedIP = "api_key_acl_trust_forwarded_ip" // API Key IP 白/黑名单是否信任转发 IP + settingKeyForwardedClientIPModeV2 = "forwarded_client_ip_mode_v2_migrated" // TOTP 双因素认证设置 SettingKeyTotpEnabled = "totp_enabled" // 是否启用 TOTP 2FA 功能 diff --git a/backend/internal/service/setting_parse.go b/backend/internal/service/setting_parse.go index a56e57784e..4481bf262d 100644 --- a/backend/internal/service/setting_parse.go +++ b/backend/internal/service/setting_parse.go @@ -54,7 +54,8 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error { SettingKeyLoginAgreementMode: defaultLoginAgreementMode, SettingKeyLoginAgreementUpdatedAt: defaultLoginAgreementDate, SettingKeyLoginAgreementDocuments: loginAgreementDocumentsJSON, - SettingKeyAPIKeyACLTrustForwardedIP: "false", + SettingKeyAPIKeyACLTrustForwardedIP: "true", + settingKeyForwardedClientIPModeV2: "true", SettingKeySiteName: "Sub2API", SettingKeySiteLogo: "", SettingKeyPurchaseSubscriptionEnabled: "false", @@ -249,7 +250,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin if value, ok := settings[SettingKeyAPIKeyACLTrustForwardedIP]; ok { apiKeyACLTrustForwardedIP = value == "true" } else if s != nil && s.cfg != nil { - apiKeyACLTrustForwardedIP = s.cfg.Security.TrustForwardedIPForAPIKeyACL + apiKeyACLTrustForwardedIP = s.cfg.ForwardedClientIPTrustEnabled() } result := &SystemSettings{ RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true", diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go index 52acc62cf9..608cc0320b 100644 --- a/backend/internal/service/setting_service.go +++ b/backend/internal/service/setting_service.go @@ -2,7 +2,6 @@ package service import ( "context" - "errors" "fmt" "sync/atomic" @@ -220,15 +219,36 @@ func (s *SettingService) LoadAPIKeyACLTrustForwardedIPSetting(ctx context.Contex if s == nil || s.cfg == nil || s.settingRepo == nil { return nil } - value, err := s.settingRepo.GetValue(ctx, SettingKeyAPIKeyACLTrustForwardedIP) + + values, err := s.settingRepo.GetMultiple(ctx, []string{ + SettingKeyAPIKeyACLTrustForwardedIP, + settingKeyForwardedClientIPModeV2, + }) if err != nil { - if errors.Is(err, ErrSettingNotFound) { - s.cfg.SetTrustForwardedIPForAPIKeyACL(s.cfg.Security.TrustForwardedIPForAPIKeyACL) - return nil - } - return fmt.Errorf("get api key acl forwarded ip setting: %w", err) + s.cfg.SetTrustForwardedIPForAPIKeyACL(false) + return fmt.Errorf("get forwarded client ip settings: %w", err) } - enabled := value == "true" + + enabled := s.cfg.Security.TrustForwardedIPForAPIKeyACL + storedValue, hasStoredValue := values[SettingKeyAPIKeyACLTrustForwardedIP] + if hasStoredValue { + enabled = storedValue == "true" + } + + if values[settingKeyForwardedClientIPModeV2] != "true" { + updates := map[string]string{settingKeyForwardedClientIPModeV2: "true"} + // Before this migration, new installations persisted false by default. + // Restore compatibility only when no trusted-proxy policy was configured. + if hasStoredValue && !enabled && !s.cfg.Server.TrustedProxiesConfigured { + enabled = true + updates[SettingKeyAPIKeyACLTrustForwardedIP] = "true" + } + if err := s.settingRepo.SetMultiple(ctx, updates); err != nil { + s.cfg.SetTrustForwardedIPForAPIKeyACL(enabled) + return fmt.Errorf("migrate forwarded client ip setting: %w", err) + } + } + s.cfg.SetTrustForwardedIPForAPIKeyACL(enabled) return nil } diff --git a/backend/internal/service/setting_service_update_test.go b/backend/internal/service/setting_service_update_test.go index 93cf8284ad..efbf646feb 100644 --- a/backend/internal/service/setting_service_update_test.go +++ b/backend/internal/service/setting_service_update_test.go @@ -5,6 +5,7 @@ package service import ( "context" "encoding/json" + "errors" "math" "strconv" "testing" @@ -87,6 +88,58 @@ func (s *settingGetAllRepoStub) Delete(ctx context.Context, key string) error { panic("unexpected Delete call") } +type forwardedIPMigrationRepoStub struct { + values map[string]string + updates map[string]string + getMultipleErr error + setMultipleErr error +} + +func (s *forwardedIPMigrationRepoStub) Get(context.Context, string) (*Setting, error) { + panic("unexpected Get call") +} + +func (s *forwardedIPMigrationRepoStub) GetValue(context.Context, string) (string, error) { + panic("unexpected GetValue call") +} + +func (s *forwardedIPMigrationRepoStub) Set(context.Context, string, string) error { + panic("unexpected Set call") +} + +func (s *forwardedIPMigrationRepoStub) GetMultiple(_ context.Context, keys []string) (map[string]string, error) { + if s.getMultipleErr != nil { + return nil, s.getMultipleErr + } + result := make(map[string]string, len(keys)) + for _, key := range keys { + if value, ok := s.values[key]; ok { + result[key] = value + } + } + return result, nil +} + +func (s *forwardedIPMigrationRepoStub) SetMultiple(_ context.Context, values map[string]string) error { + if s.setMultipleErr != nil { + return s.setMultipleErr + } + s.updates = make(map[string]string, len(values)) + for key, value := range values { + s.values[key] = value + s.updates[key] = value + } + return nil +} + +func (s *forwardedIPMigrationRepoStub) GetAll(context.Context) (map[string]string, error) { + panic("unexpected GetAll call") +} + +func (s *forwardedIPMigrationRepoStub) Delete(context.Context, string) error { + panic("unexpected Delete call") +} + type settingAntigravityUARepoStub struct { values map[string]string } @@ -519,6 +572,117 @@ func TestSettingService_ParseSettings_APIKeyACLTrustForwardedIPFallsBackToConfig require.True(t, got.APIKeyACLTrustForwardedIP) } +func TestSettingService_ParseSettings_APIKeyACLTrustForwardedIPUsesStoredValue(t *testing.T) { + cfg := &config.Config{} + cfg.SetTrustForwardedIPForAPIKeyACL(true) + svc := NewSettingService(&settingUpdateRepoStub{}, cfg) + + got := svc.parseSettings(map[string]string{SettingKeyAPIKeyACLTrustForwardedIP: "false"}) + + require.False(t, got.APIKeyACLTrustForwardedIP) +} + +func TestSettingService_LoadAPIKeyACLTrustForwardedIPSettingMigration(t *testing.T) { + tests := []struct { + name string + values map[string]string + trustedProxiesSet bool + configDefault bool + wantEnabled bool + wantForwardedIPUpdate string + wantMigrationMarkerSet bool + }{ + { + name: "missing setting follows configured default", + values: map[string]string{}, + configDefault: true, + wantEnabled: true, + wantMigrationMarkerSet: true, + }, + { + name: "legacy false without proxy config migrates to compatibility", + values: map[string]string{SettingKeyAPIKeyACLTrustForwardedIP: "false"}, + wantEnabled: true, + wantForwardedIPUpdate: "true", + wantMigrationMarkerSet: true, + }, + { + name: "legacy false with explicit proxy config stays secure", + values: map[string]string{SettingKeyAPIKeyACLTrustForwardedIP: "false"}, + trustedProxiesSet: true, + wantEnabled: false, + wantMigrationMarkerSet: true, + }, + { + name: "completed migration preserves later false choice", + values: map[string]string{ + SettingKeyAPIKeyACLTrustForwardedIP: "false", + settingKeyForwardedClientIPModeV2: "true", + }, + wantEnabled: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + repo := &forwardedIPMigrationRepoStub{values: test.values} + cfg := &config.Config{Server: config.ServerConfig{TrustedProxiesConfigured: test.trustedProxiesSet}} + cfg.Security.TrustForwardedIPForAPIKeyACL = test.configDefault + svc := NewSettingService(repo, cfg) + + require.NoError(t, svc.LoadAPIKeyACLTrustForwardedIPSetting(context.Background())) + require.Equal(t, test.wantEnabled, cfg.TrustForwardedIPForAPIKeyACL()) + require.Equal(t, test.wantForwardedIPUpdate, repo.updates[SettingKeyAPIKeyACLTrustForwardedIP]) + if test.wantMigrationMarkerSet { + require.Equal(t, "true", repo.updates[settingKeyForwardedClientIPModeV2]) + } else { + require.Nil(t, repo.updates) + } + }) + } +} + +func TestSettingService_LoadAPIKeyACLTrustForwardedIPSettingReadFailureFailsClosed(t *testing.T) { + repo := &forwardedIPMigrationRepoStub{ + getMultipleErr: errors.New("database unavailable"), + } + cfg := &config.Config{} + cfg.SetTrustForwardedIPForAPIKeyACL(true) + svc := NewSettingService(repo, cfg) + + err := svc.LoadAPIKeyACLTrustForwardedIPSetting(context.Background()) + + require.ErrorContains(t, err, "get forwarded client ip settings") + require.False(t, cfg.TrustForwardedIPForAPIKeyACL()) +} + +func TestSettingService_LoadAPIKeyACLTrustForwardedIPSettingWriteFailureUsesComputedMode(t *testing.T) { + tests := []struct { + name string + trustedProxiesSet bool + wantEnabled bool + }{ + {name: "compatibility migration remains effective", wantEnabled: true}, + {name: "explicit proxy policy remains secure", trustedProxiesSet: true, wantEnabled: false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + repo := &forwardedIPMigrationRepoStub{ + values: map[string]string{SettingKeyAPIKeyACLTrustForwardedIP: "false"}, + setMultipleErr: errors.New("database unavailable"), + } + cfg := &config.Config{Server: config.ServerConfig{TrustedProxiesConfigured: test.trustedProxiesSet}} + svc := NewSettingService(repo, cfg) + + err := svc.LoadAPIKeyACLTrustForwardedIPSetting(context.Background()) + + require.ErrorContains(t, err, "migrate forwarded client ip setting") + require.Equal(t, test.wantEnabled, cfg.TrustForwardedIPForAPIKeyACL()) + }) + } +} + func TestSettingService_GetAntigravityUserAgentVersion_Precedence(t *testing.T) { t.Run("后台设置优先", func(t *testing.T) { svc := NewSettingService(&settingAntigravityUARepoStub{values: map[string]string{