mirror of
https://github.com/larksuite/cli.git
synced 2026-07-08 02:00:19 +08:00
fix(config): save empty config before clearing keychain entries (#291)
* fix(config): save empty config before clearing keychain entries
This commit is contained in:
@@ -6,6 +6,8 @@ package config
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -21,6 +23,17 @@ func (n *noopConfigKeychain) Get(service, account string) (string, error) { retu
|
||||
func (n *noopConfigKeychain) Set(service, account, value string) error { return nil }
|
||||
func (n *noopConfigKeychain) Remove(service, account string) error { return nil }
|
||||
|
||||
type recordingConfigKeychain struct {
|
||||
removed []string
|
||||
}
|
||||
|
||||
func (r *recordingConfigKeychain) Get(service, account string) (string, error) { return "", nil }
|
||||
func (r *recordingConfigKeychain) Set(service, account, value string) error { return nil }
|
||||
func (r *recordingConfigKeychain) Remove(service, account string) error {
|
||||
r.removed = append(r.removed, service+":"+account)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestConfigInitCmd_FlagParsing(t *testing.T) {
|
||||
f, _, _, _ := cmdutil.TestFactory(t, nil)
|
||||
f.IOStreams.In = strings.NewReader("secret123\n")
|
||||
@@ -221,6 +234,66 @@ func TestConfigRemoveCmd_FlagParsing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigRemoveRun_SaveFailurePreservesExistingConfigAndSecrets(t *testing.T) {
|
||||
configDir := t.TempDir()
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", configDir)
|
||||
|
||||
multi := &core.MultiAppConfig{
|
||||
Apps: []core.AppConfig{{
|
||||
AppId: "app-test",
|
||||
AppSecret: core.SecretInput{
|
||||
Ref: &core.SecretRef{Source: "keychain", ID: "appsecret:app-test"},
|
||||
},
|
||||
Brand: core.BrandFeishu,
|
||||
Users: []core.AppUser{{UserOpenId: "ou_1", UserName: "Tester"}},
|
||||
}},
|
||||
}
|
||||
if err := core.SaveMultiAppConfig(multi); err != nil {
|
||||
t.Fatalf("SaveMultiAppConfig() error = %v", err)
|
||||
}
|
||||
|
||||
kc := &recordingConfigKeychain{}
|
||||
f, _, _, _ := cmdutil.TestFactory(t, nil)
|
||||
f.Keychain = kc
|
||||
|
||||
// Make subsequent config saves fail while keeping the existing config readable.
|
||||
if err := os.Chmod(configDir, 0500); err != nil {
|
||||
t.Fatalf("Chmod(%s) error = %v", configDir, err)
|
||||
}
|
||||
defer os.Chmod(configDir, 0700)
|
||||
|
||||
err := configRemoveRun(&ConfigRemoveOptions{Factory: f})
|
||||
if err == nil {
|
||||
t.Fatal("expected save failure")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to save config") {
|
||||
t.Fatalf("error = %v, want failed to save config", err)
|
||||
}
|
||||
if len(kc.removed) != 0 {
|
||||
t.Fatalf("expected no keychain cleanup before successful save, got removals: %v", kc.removed)
|
||||
}
|
||||
|
||||
// Restore permissions and confirm the original config is still intact.
|
||||
if err := os.Chmod(configDir, 0700); err != nil {
|
||||
t.Fatalf("restore Chmod(%s) error = %v", configDir, err)
|
||||
}
|
||||
saved, err := core.LoadMultiAppConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("LoadMultiAppConfig() error = %v", err)
|
||||
}
|
||||
if saved == nil || len(saved.Apps) != 1 || saved.Apps[0].AppId != "app-test" {
|
||||
t.Fatalf("saved config = %#v, want original single app preserved", saved)
|
||||
}
|
||||
if got := saved.Apps[0].AppSecret.Ref; got == nil || got.ID != "appsecret:app-test" {
|
||||
t.Fatalf("saved app secret ref = %#v, want preserved keychain ref", got)
|
||||
}
|
||||
|
||||
configPath := filepath.Join(configDir, "config.json")
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
t.Fatalf("expected existing config file to remain, stat error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveAsProfile_RejectsProfileNameCollisionWithExistingAppID(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
|
||||
@@ -44,19 +44,21 @@ func configRemoveRun(opts *ConfigRemoveOptions) error {
|
||||
return output.ErrValidation("not configured yet")
|
||||
}
|
||||
|
||||
// Clean up keychain entries for all apps
|
||||
for _, app := range config.Apps {
|
||||
core.RemoveSecretStore(app.AppSecret, f.Keychain)
|
||||
for _, user := range app.Users {
|
||||
auth.RemoveStoredToken(app.AppId, user.UserOpenId)
|
||||
}
|
||||
}
|
||||
|
||||
// Save empty config
|
||||
// Save empty config first. If this fails, keep secrets and tokens intact so the
|
||||
// existing config can still be retried instead of ending up half-removed.
|
||||
empty := &core.MultiAppConfig{Apps: []core.AppConfig{}}
|
||||
if err := core.SaveMultiAppConfig(empty); err != nil {
|
||||
return output.Errorf(output.ExitInternal, "internal", "failed to save config: %v", err)
|
||||
}
|
||||
|
||||
// Clean up keychain entries for all apps after config is cleared.
|
||||
for _, app := range config.Apps {
|
||||
core.RemoveSecretStore(app.AppSecret, f.Keychain)
|
||||
for _, user := range app.Users {
|
||||
_ = auth.RemoveStoredToken(app.AppId, user.UserOpenId)
|
||||
}
|
||||
}
|
||||
|
||||
output.PrintSuccess(f.IOStreams.ErrOut, "Configuration removed")
|
||||
userCount := 0
|
||||
for _, app := range config.Apps {
|
||||
|
||||
Reference in New Issue
Block a user