jenkins
This commit is contained in:
parent
dce95412dd
commit
d83720c8fd
|
|
@ -37,6 +37,29 @@ type SecurityConfig struct {
|
|||
ForceNewUserChangePassword bool
|
||||
}
|
||||
|
||||
// ErrDatabaseNotInitialized is returned by storage-backed settings that are
|
||||
// intentionally unavailable during the first-install bootstrap phase.
|
||||
var ErrDatabaseNotInitialized = errors.New("database not initialized")
|
||||
|
||||
// DefaultSecurityConfig matches the seed values used for a fresh installation.
|
||||
// It is owned by biz so bootstrap callers do not need to depend on data.
|
||||
func DefaultSecurityConfig() *SecurityConfig {
|
||||
return &SecurityConfig{
|
||||
ID: 1,
|
||||
CaptchaTimeout: 3600,
|
||||
KeyLong: 6,
|
||||
ImgWidth: 240,
|
||||
ImgHeight: 80,
|
||||
PwdMinLength: 8,
|
||||
LimitWindow: 60,
|
||||
LimitCount: 30,
|
||||
LockThreshold: 5,
|
||||
LockDuration: 30,
|
||||
PwdExpireDays: 90,
|
||||
ForceNewUserChangePassword: false,
|
||||
}
|
||||
}
|
||||
|
||||
type SecurityRepo interface {
|
||||
SecurityConfig(context.Context) (*SecurityConfig, error)
|
||||
SaveSecurityConfig(context.Context, *SecurityConfig) error
|
||||
|
|
@ -99,8 +122,17 @@ func (uc *SecurityUsecase) Current(ctx context.Context) (*SecurityConfig, error)
|
|||
uc.mu.RUnlock()
|
||||
value, err := uc.repo.SecurityConfig(ctx)
|
||||
if err != nil {
|
||||
// The login page and captcha are also the entry point for first install.
|
||||
// Do not cache this fallback: after initdb succeeds the next request must
|
||||
// load the persisted row instead of serving bootstrap defaults forever.
|
||||
if errors.Is(err, ErrDatabaseNotInitialized) {
|
||||
return DefaultSecurityConfig(), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if value == nil {
|
||||
return nil, errors.New("security config is nil")
|
||||
}
|
||||
uc.mu.Lock()
|
||||
copy := *value
|
||||
uc.cachedConfig = ©
|
||||
|
|
|
|||
|
|
@ -15,6 +15,27 @@ type securityUpdateRepo struct {
|
|||
persistedCopy *SecurityConfig
|
||||
}
|
||||
|
||||
type bootstrapSecurityRepo struct {
|
||||
initialized bool
|
||||
calls int
|
||||
}
|
||||
|
||||
func (r *bootstrapSecurityRepo) SecurityConfig(context.Context) (*SecurityConfig, error) {
|
||||
r.calls++
|
||||
if !r.initialized {
|
||||
return nil, ErrDatabaseNotInitialized
|
||||
}
|
||||
return &SecurityConfig{ID: 1, LimitCount: 42}, nil
|
||||
}
|
||||
|
||||
func (*bootstrapSecurityRepo) SaveSecurityConfig(context.Context, *SecurityConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*bootstrapSecurityRepo) BackfillPasswordUpdatedAt(context.Context, time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *securityUpdateRepo) SecurityConfig(context.Context) (*SecurityConfig, error) {
|
||||
r.callOrder = append(r.callOrder, "get")
|
||||
copy := *r.current
|
||||
|
|
@ -110,3 +131,31 @@ func TestSecurityReadsPersistedValueWhileCurrentUsesCache(t *testing.T) {
|
|||
t.Fatalf("Security() returned stale value: %d", fresh.LimitCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentUsesBootstrapDefaultsWithoutCachingThem(t *testing.T) {
|
||||
repo := &bootstrapSecurityRepo{}
|
||||
uc := NewSecurityUsecase(repo, nil, nil, nil)
|
||||
|
||||
bootstrap, err := uc.Current(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Current() during bootstrap error = %v", err)
|
||||
}
|
||||
if bootstrap.ID != 1 || bootstrap.KeyLong != 6 || bootstrap.ImgWidth != 240 || bootstrap.ImgHeight != 80 || bootstrap.LimitEnable {
|
||||
t.Fatalf("bootstrap config = %+v", bootstrap)
|
||||
}
|
||||
if repo.calls != 1 {
|
||||
t.Fatalf("bootstrap repository calls = %d, want 1", repo.calls)
|
||||
}
|
||||
|
||||
repo.initialized = true
|
||||
active, err := uc.Current(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Current() after initialization error = %v", err)
|
||||
}
|
||||
if active.LimitCount != 42 {
|
||||
t.Fatalf("active config = %+v, want persisted config", active)
|
||||
}
|
||||
if repo.calls != 2 {
|
||||
t.Fatalf("repository calls = %d, want fallback to be uncached", repo.calls)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ type SecurityConfigPO struct {
|
|||
func (SecurityConfigPO) TableName() string { return "sys_security_config" }
|
||||
|
||||
func DefaultSecurityConfig() SecurityConfigPO {
|
||||
return SecurityConfigPO{ID: 1, CaptchaTimeout: 3600, KeyLong: 6, ImgWidth: 240, ImgHeight: 80, PwdMinLength: 8, LimitWindow: 60, LimitCount: 30, LockThreshold: 5, LockDuration: 30, PwdExpireDays: 90}
|
||||
return securityToPO(system.DefaultSecurityConfig())
|
||||
}
|
||||
|
||||
type securityRepo struct{ data DatabaseProvider }
|
||||
|
|
@ -60,7 +60,7 @@ func securityToPO(v *system.SecurityConfig) SecurityConfigPO {
|
|||
|
||||
func (r *securityRepo) SecurityConfig(ctx context.Context) (*system.SecurityConfig, error) {
|
||||
if !r.data.DatabaseReady() {
|
||||
return nil, errors.New("数据库未初始化")
|
||||
return nil, system.ErrDatabaseNotInitialized
|
||||
}
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var po SecurityConfigPO
|
||||
|
|
|
|||
Loading…
Reference in New Issue