@@ -16,9 +16,9 @@ import (
1616 "golang.org/x/exp/slices"
1717 "tailscale.com/envknob"
1818 "tailscale.com/ipn"
19- "tailscale.com/tailcfg"
2019 "tailscale.com/types/logger"
2120 "tailscale.com/util/clientmetric"
21+ "tailscale.com/util/cmpx"
2222 "tailscale.com/util/winutil"
2323)
2424
@@ -33,15 +33,9 @@ type profileManager struct {
3333 logf logger.Logf
3434
3535 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.
4539}
4640
4741func (pm * profileManager ) dlogf (format string , args ... any ) {
@@ -107,40 +101,45 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
107101 }
108102 pm .currentProfile = prof
109103 pm .prefs = prefs
110- pm .isNewProfile = false
111104 return nil
112105}
113106
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 ) {
117110 for _ , p := range pm .knownProfiles {
118- if p .LocalUserID == pm .currentUserID && f ( p ) {
111+ if p .LocalUserID == pm .currentUserID {
119112 out = append (out , p )
120113 }
121114 }
115+ slices .SortFunc (out , func (a , b * ipn.LoginProfile ) int {
116+ return cmpx .Compare (a .Name , b .Name )
117+ })
122118 return out
123119}
124120
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+ }
130131 }
131- return pm .matchingProfiles (func (p * ipn.LoginProfile ) bool {
132- return p .NodeID == nodeID && p .ControlURL == controlURL
133- })
132+ return out
134133}
135134
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 {
142139 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 )
144143 })
145144}
146145
@@ -206,40 +205,47 @@ func init() {
206205// It also saves the prefs to the StateStore. It stores a copy of the
207206// provided prefs, which may be accessed via CurrentPrefs.
208207func (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
211210 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 ())
213213 }
214214 up := newPersist .UserProfile
215215 if up .DisplayName == "" {
216216 up .DisplayName = up .LoginName
217217 }
218218 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
232230 }
233- cp = existing [0 ]
231+ // Remove the profile, knownProfiles will be persisted below.
232+ delete (pm .knownProfiles , p .ID )
234233 }
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 )
235237 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.
236241 }
237- if prefs .ProfileName () != "" {
238- cp .Name = prefs .ProfileName ()
242+
243+ if prefs .ProfileName != "" {
244+ cp .Name = prefs .ProfileName
239245 } else {
240246 cp .Name = up .LoginName
241247 }
242- cp .ControlURL = prefs .ControlURL ()
248+ cp .ControlURL = prefs .ControlURL
243249 cp .UserProfile = newPersist .UserProfile
244250 cp .NodeID = newPersist .NodeID
245251 pm .knownProfiles [cp .ID ] = cp
@@ -250,7 +256,7 @@ func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView) error {
250256 if err := pm .setAsUserSelectedProfileLocked (); err != nil {
251257 return err
252258 }
253- if err := pm .setPrefsLocked (prefs ); err != nil {
259+ if err := pm .setPrefsLocked (prefs . View () ); err != nil {
254260 return err
255261 }
256262 return nil
@@ -273,7 +279,7 @@ func newUnusedID(knownProfiles map[ipn.ProfileID]*ipn.LoginProfile) (ipn.Profile
273279// is not new.
274280func (pm * profileManager ) setPrefsLocked (clonedPrefs ipn.PrefsView ) error {
275281 pm .prefs = clonedPrefs
276- if pm .isNewProfile {
282+ if pm .currentProfile . ID == "" {
277283 return nil
278284 }
279285 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
295301
296302// Profiles returns the list of known profiles.
297303func (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 {
304307 out = append (out , * p )
305308 }
306309 return out
@@ -328,7 +331,6 @@ func (pm *profileManager) SwitchProfile(id ipn.ProfileID) error {
328331 }
329332 pm .prefs = prefs
330333 pm .currentProfile = kp
331- pm .isNewProfile = false
332334 return pm .setAsUserSelectedProfileLocked ()
333335}
334336
@@ -380,7 +382,7 @@ var errProfileNotFound = errors.New("profile not found")
380382func (pm * profileManager ) DeleteProfile (id ipn.ProfileID ) error {
381383 metricDeleteProfile .Add (1 )
382384
383- if id == "" && pm . isNewProfile {
385+ if id == "" {
384386 // Deleting the in-memory only new profile, just create a new one.
385387 pm .NewProfile ()
386388 return nil
@@ -431,7 +433,6 @@ func (pm *profileManager) NewProfile() {
431433 metricNewProfile .Add (1 )
432434
433435 pm .prefs = defaultPrefs
434- pm .isNewProfile = true
435436 pm .currentProfile = & ipn.LoginProfile {}
436437}
437438
0 commit comments