返回 DeepSeek-Reasonix
credential_key_claim.go
根目录 / internal / config / credential_key_claim.go
1 package config
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "runtime"
8 "strings"
9 )
10
11 // ErrCredentialKeyInUse marks a named credential variable that a connection
12 // may not claim; CredentialKeyInUseError says who holds it.
13 var ErrCredentialKeyInUse = errors.New("credential variable already in use")
14
15 // CredentialKeyHolder names why a variable is unavailable.
16 type CredentialKeyHolder uint8
17
18 const (
19 // CredentialKeyHeldByProvider: another provider entry references the variable.
20 CredentialKeyHeldByProvider CredentialKeyHolder = iota + 1
21 // CredentialKeyHeldBySetting: a bot or remote-host setting reads the variable.
22 CredentialKeyHeldBySetting
23 // CredentialKeyHeldByStore: the credential store already has a value or tombstone for it.
24 CredentialKeyHeldByStore
25 // CredentialKeyHeldByEnvironment: the process environment sets it from outside the store.
26 CredentialKeyHeldByEnvironment
27 )
28
29 type CredentialKeyInUseError struct {
30 Key string
31 Holder CredentialKeyHolder
32 Provider string // set when Holder is CredentialKeyHeldByProvider
33 }
34
35 func (e *CredentialKeyInUseError) Error() string {
36 switch e.Holder {
37 case CredentialKeyHeldByProvider:
38 return fmt.Sprintf("%s is referenced by provider %q", e.Key, e.Provider)
39 case CredentialKeyHeldBySetting:
40 return fmt.Sprintf("%s is read by a bot or remote-host setting", e.Key)
41 case CredentialKeyHeldByEnvironment:
42 return fmt.Sprintf("%s is already set in the environment", e.Key)
43 default:
44 return fmt.Sprintf("%s already has a stored credential", e.Key)
45 }
46 }
47
48 func (e *CredentialKeyInUseError) Unwrap() error { return ErrCredentialKeyInUse }
49
50 // sameCredentialName compares variable names the way the OS environment does.
51 func sameCredentialName(a, b string) bool {
52 if runtime.GOOS == "windows" {
53 return strings.EqualFold(a, b)
54 }
55 return a == b
56 }
57
58 func environmentHasName(key string) bool {
59 for _, kv := range os.Environ() {
60 name, _, _ := strings.Cut(kv, "=")
61 if name != "" && sameCredentialName(name, key) {
62 return true
63 }
64 }
65 return false
66 }
67
68 // CredentialKeyClaimable reports whether provider may write its key under key
69 // without changing what any other reader of that variable resolves. A stored
70 // value or tombstone may belong to a config this one cannot see.
71 func (c *Config) CredentialKeyClaimable(key, provider string) error {
72 key = strings.TrimSpace(key)
73 if !isCredentialKey(key) {
74 return fmt.Errorf("invalid credential key %q", key)
75 }
76 for _, p := range c.Providers {
77 if p.Name != provider && sameCredentialName(strings.TrimSpace(p.APIKeyEnv), key) {
78 return &CredentialKeyInUseError{Key: key, Holder: CredentialKeyHeldByProvider, Provider: p.Name}
79 }
80 }
81 others := *c
82 others.Providers = nil
83 for _, name := range credentialEnvNamesFromConfig(&others) {
84 if sameCredentialName(name, key) {
85 return &CredentialKeyInUseError{Key: key, Holder: CredentialKeyHeldBySetting}
86 }
87 }
88 if credentialCurrentStoreHasKey(key) || credentialCurrentStoreClearedKey(key) {
89 return &CredentialKeyInUseError{Key: key, Holder: CredentialKeyHeldByStore}
90 }
91 if environmentHasName(key) {
92 return &CredentialKeyInUseError{Key: key, Holder: CredentialKeyHeldByEnvironment}
93 }
94 return nil
95 }
96
97 // StageNamedModelCredentialLocked is StageModelCredentialLocked for a name the
98 // user chose. It refuses a name CredentialKeyClaimable rejects, so the no-
99 // overwrite and rollback guarantees of a fresh slot still hold.
100 func (c *Config) StageNamedModelCredentialLocked(key, provider, value string) (string, error) {
101 if err := c.CredentialKeyClaimable(key, provider); err != nil {
102 return "", err
103 }
104 return c.stageModelCredentialLocked(strings.TrimSpace(key), value)
105 }
106
107 // configReferencesCredential reports whether the config at path reads key.
108 // A config that cannot be parsed counts as referencing it: removal needs proof.
109 func configReferencesCredential(path, key string) bool {
110 if _, err := os.Stat(path); os.IsNotExist(err) {
111 return false
112 }
113 cfg, err := LoadForEditWithoutCredentialsReadOnlyStrict(path)
114 if err != nil {
115 return true
116 }
117 for _, name := range credentialEnvNamesFromConfig(cfg) {
118 if sameCredentialName(name, key) {
119 return true
120 }
121 }
122 return false
123 }
124
125 func stagedCredentialDigest(value string) (string, error) {
126 return ModelSettingsRequestDigest([]byte(strings.TrimSpace(value)))
127 }
128
129 // stagedValueUnchanged reports whether slot still holds what this edit staged.
130 // No recorded digest means a journal from before digests existed.
131 func stagedValueUnchanged(slot, digest string) bool {
132 if digest == "" {
133 return true
134 }
135 value, ok := envFileValue(UserCredentialsPath(), slot)
136 if !ok {
137 return !envFileHasClearedKey(UserCredentialsPath(), slot)
138 }
139 got, err := stagedCredentialDigest(value)
140 return err == nil && got == digest
141 }
142
142 lines GO