@@ -16,9 +16,9 @@ import (
16
16
"golang.org/x/exp/slices"
17
17
"tailscale.com/envknob"
18
18
"tailscale.com/ipn"
19
- "tailscale.com/tailcfg"
20
19
"tailscale.com/types/logger"
21
20
"tailscale.com/util/clientmetric"
21
+ "tailscale.com/util/cmpx"
22
22
"tailscale.com/util/winutil"
23
23
)
24
24
@@ -33,15 +33,9 @@ type profileManager struct {
33
33
logf logger.Logf
34
34
35
35
currentUserID ipn.WindowsUserID
36
- knownProfiles map [ipn.ProfileID ]* ipn.LoginProfile
37
- currentProfile * ipn.LoginProfile // always non-nil
38
- prefs ipn.PrefsView // always Valid.
39
-
40
- // isNewProfile is a sentinel value that indicates that the
41
- // current profile is new and has not been saved to disk yet.
42
- // It is reset to false after a call to SetPrefs with a filled
43
- // in LoginName.
44
- isNewProfile bool
36
+ knownProfiles map [ipn.ProfileID ]* ipn.LoginProfile // always non-nil
37
+ currentProfile * ipn.LoginProfile // always non-nil
38
+ prefs ipn.PrefsView // always Valid.
45
39
}
46
40
47
41
func (pm * profileManager ) dlogf (format string , args ... any ) {
@@ -107,40 +101,45 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
107
101
}
108
102
pm .currentProfile = prof
109
103
pm .prefs = prefs
110
- pm .isNewProfile = false
111
104
return nil
112
105
}
113
106
114
- // matchingProfiles returns all profiles that match the given predicate and
115
- // belong to the currentUserID .
116
- func (pm * profileManager ) matchingProfiles ( f func ( * ipn. LoginProfile ) bool ) (out []* ipn.LoginProfile ) {
107
+ // allProfiles returns all profiles that belong to the currentUserID.
108
+ // The returned profiles are sorted by Name .
109
+ func (pm * profileManager ) allProfiles ( ) (out []* ipn.LoginProfile ) {
117
110
for _ , p := range pm .knownProfiles {
118
- if p .LocalUserID == pm .currentUserID && f ( p ) {
111
+ if p .LocalUserID == pm .currentUserID {
119
112
out = append (out , p )
120
113
}
121
114
}
115
+ slices .SortFunc (out , func (a , b * ipn.LoginProfile ) int {
116
+ return cmpx .Compare (a .Name , b .Name )
117
+ })
122
118
return out
123
119
}
124
120
125
- // findProfilesByNodeID returns all profiles that have the provided nodeID and
126
- // belong to the same control server.
127
- func (pm * profileManager ) findProfilesByNodeID (controlURL string , nodeID tailcfg.StableNodeID ) []* ipn.LoginProfile {
128
- if nodeID .IsZero () {
129
- return nil
121
+ // matchingProfiles returns all profiles that match the given predicate and
122
+ // belong to the currentUserID.
123
+ // The returned profiles are sorted by Name.
124
+ func (pm * profileManager ) matchingProfiles (f func (* ipn.LoginProfile ) bool ) (out []* ipn.LoginProfile ) {
125
+ all := pm .allProfiles ()
126
+ out = all [:0 ]
127
+ for _ , p := range all {
128
+ if f (p ) {
129
+ out = append (out , p )
130
+ }
130
131
}
131
- return pm .matchingProfiles (func (p * ipn.LoginProfile ) bool {
132
- return p .NodeID == nodeID && p .ControlURL == controlURL
133
- })
132
+ return out
134
133
}
135
134
136
- // findProfilesByUserID returns all profiles that have the provided userID and
137
- // belong to the same control server.
138
- func (pm * profileManager ) findProfilesByUserID (controlURL string , userID tailcfg.UserID ) []* ipn.LoginProfile {
139
- if userID .IsZero () {
140
- return nil
141
- }
135
+ // findMatchinProfiles returns all profiles that represent the same node/user as
136
+ // prefs.
137
+ // The returned profiles are sorted by Name.
138
+ func (pm * profileManager ) findMatchingProfiles (prefs * ipn.Prefs ) []* ipn.LoginProfile {
142
139
return pm .matchingProfiles (func (p * ipn.LoginProfile ) bool {
143
- return p .UserProfile .ID == userID && p .ControlURL == controlURL
140
+ return p .ControlURL == prefs .ControlURL &&
141
+ (p .UserProfile .ID == prefs .Persist .UserProfile .ID ||
142
+ p .NodeID == prefs .Persist .NodeID )
144
143
})
145
144
}
146
145
@@ -206,40 +205,47 @@ func init() {
206
205
// It also saves the prefs to the StateStore. It stores a copy of the
207
206
// provided prefs, which may be accessed via CurrentPrefs.
208
207
func (pm * profileManager ) SetPrefs (prefsIn ipn.PrefsView ) error {
209
- prefs := prefsIn .AsStruct (). View ()
210
- newPersist := prefs .Persist (). AsStruct ()
208
+ prefs := prefsIn .AsStruct ()
209
+ newPersist := prefs .Persist
211
210
if newPersist == nil || newPersist .NodeID == "" || newPersist .UserProfile .LoginName == "" {
212
- return pm .setPrefsLocked (prefs )
211
+ // We don't know anything about this profile, so ignore it for now.
212
+ return pm .setPrefsLocked (prefs .View ())
213
213
}
214
214
up := newPersist .UserProfile
215
215
if up .DisplayName == "" {
216
216
up .DisplayName = up .LoginName
217
217
}
218
218
cp := pm .currentProfile
219
- if pm .isNewProfile {
220
- pm .isNewProfile = false
221
- // Check if we already have a profile for this user.
222
- existing := pm .findProfilesByUserID (prefs .ControlURL (), newPersist .UserProfile .ID )
223
- // Also check if we have a profile with the same NodeID.
224
- existing = append (existing , pm .findProfilesByNodeID (prefs .ControlURL (), newPersist .NodeID )... )
225
- if len (existing ) == 0 {
226
- cp .ID , cp .Key = newUnusedID (pm .knownProfiles )
227
- } else {
228
- // Only one profile per user/nodeID should exist.
229
- for _ , p := range existing [1 :] {
230
- // Best effort cleanup.
231
- pm .DeleteProfile (p .ID )
219
+ // Check if we already have an existing profile that matches the user/node.
220
+ if existing := pm .findMatchingProfiles (prefs ); len (existing ) > 0 {
221
+ // We already have a profile for this user/node we should reuse it. Also
222
+ // cleanup any other duplicate profiles.
223
+ cp = existing [0 ]
224
+ existing = existing [1 :]
225
+ for _ , p := range existing {
226
+ // Clear the state.
227
+ if err := pm .store .WriteState (p .Key , nil ); err != nil {
228
+ // We couldn't delete the state, so keep the profile around.
229
+ continue
232
230
}
233
- cp = existing [0 ]
231
+ // Remove the profile, knownProfiles will be persisted below.
232
+ delete (pm .knownProfiles , p .ID )
234
233
}
234
+ } else if cp .ID == "" {
235
+ // We didn't have an existing profile, so create a new one.
236
+ cp .ID , cp .Key = newUnusedID (pm .knownProfiles )
235
237
cp .LocalUserID = pm .currentUserID
238
+ } else {
239
+ // This means that there was a force-reauth as a new node that
240
+ // we haven't seen before.
236
241
}
237
- if prefs .ProfileName () != "" {
238
- cp .Name = prefs .ProfileName ()
242
+
243
+ if prefs .ProfileName != "" {
244
+ cp .Name = prefs .ProfileName
239
245
} else {
240
246
cp .Name = up .LoginName
241
247
}
242
- cp .ControlURL = prefs .ControlURL ()
248
+ cp .ControlURL = prefs .ControlURL
243
249
cp .UserProfile = newPersist .UserProfile
244
250
cp .NodeID = newPersist .NodeID
245
251
pm .knownProfiles [cp .ID ] = cp
@@ -250,7 +256,7 @@ func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView) error {
250
256
if err := pm .setAsUserSelectedProfileLocked (); err != nil {
251
257
return err
252
258
}
253
- if err := pm .setPrefsLocked (prefs ); err != nil {
259
+ if err := pm .setPrefsLocked (prefs . View () ); err != nil {
254
260
return err
255
261
}
256
262
return nil
@@ -273,7 +279,7 @@ func newUnusedID(knownProfiles map[ipn.ProfileID]*ipn.LoginProfile) (ipn.Profile
273
279
// is not new.
274
280
func (pm * profileManager ) setPrefsLocked (clonedPrefs ipn.PrefsView ) error {
275
281
pm .prefs = clonedPrefs
276
- if pm .isNewProfile {
282
+ if pm .currentProfile . ID == "" {
277
283
return nil
278
284
}
279
285
if err := pm .writePrefsToStore (pm .currentProfile .Key , pm .prefs ); err != nil {
@@ -295,12 +301,9 @@ func (pm *profileManager) writePrefsToStore(key ipn.StateKey, prefs ipn.PrefsVie
295
301
296
302
// Profiles returns the list of known profiles.
297
303
func (pm * profileManager ) Profiles () []ipn.LoginProfile {
298
- profiles := pm .matchingProfiles (func (* ipn.LoginProfile ) bool { return true })
299
- slices .SortFunc (profiles , func (a , b * ipn.LoginProfile ) int {
300
- return strings .Compare (a .Name , b .Name )
301
- })
302
- out := make ([]ipn.LoginProfile , 0 , len (profiles ))
303
- for _ , p := range profiles {
304
+ allProfiles := pm .allProfiles ()
305
+ out := make ([]ipn.LoginProfile , 0 , len (allProfiles ))
306
+ for _ , p := range allProfiles {
304
307
out = append (out , * p )
305
308
}
306
309
return out
@@ -328,7 +331,6 @@ func (pm *profileManager) SwitchProfile(id ipn.ProfileID) error {
328
331
}
329
332
pm .prefs = prefs
330
333
pm .currentProfile = kp
331
- pm .isNewProfile = false
332
334
return pm .setAsUserSelectedProfileLocked ()
333
335
}
334
336
@@ -380,7 +382,7 @@ var errProfileNotFound = errors.New("profile not found")
380
382
func (pm * profileManager ) DeleteProfile (id ipn.ProfileID ) error {
381
383
metricDeleteProfile .Add (1 )
382
384
383
- if id == "" && pm . isNewProfile {
385
+ if id == "" {
384
386
// Deleting the in-memory only new profile, just create a new one.
385
387
pm .NewProfile ()
386
388
return nil
@@ -431,7 +433,6 @@ func (pm *profileManager) NewProfile() {
431
433
metricNewProfile .Add (1 )
432
434
433
435
pm .prefs = defaultPrefs
434
- pm .isNewProfile = true
435
436
pm .currentProfile = & ipn.LoginProfile {}
436
437
}
437
438
0 commit comments