@@ -40,8 +40,8 @@ type profileManager struct {
4040
4141 currentUserID ipn.WindowsUserID
4242 knownProfiles map [ipn.ProfileID ]ipn.LoginProfileView // always non-nil
43- currentProfile ipn.LoginProfileView // always Valid.
44- prefs ipn.PrefsView // always Valid.
43+ currentProfile ipn.LoginProfileView // always Valid (once [newProfileManager] returns) .
44+ prefs ipn.PrefsView // always Valid (once [newProfileManager] returns) .
4545
4646 // extHost is the bridge between [profileManager] and the registered [ipnext.Extension]s.
4747 // It may be nil in tests. A nil pointer is a valid, no-op host.
@@ -111,14 +111,17 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) {
111111//
112112// It returns the current profile and whether the call resulted in a profile change,
113113// or an error if the specified profile does not exist or its prefs could not be loaded.
114+ //
115+ // It may be called during [profileManager] initialization before [newProfileManager] returns
116+ // and must check whether pm.currentProfile is Valid before using it.
114117func (pm * profileManager ) SwitchToProfile (profile ipn.LoginProfileView ) (cp ipn.LoginProfileView , changed bool , err error ) {
115118 prefs := defaultPrefs
116119 switch {
117120 case ! profile .Valid ():
118121 // Create a new profile that is not associated with any user.
119122 profile = pm .NewProfileForUser ("" )
120123 case profile == pm .currentProfile ,
121- profile .ID () != "" && profile .ID () == pm .currentProfile .ID (),
124+ profile .ID () != "" && pm . currentProfile . Valid () && profile .ID () == pm .currentProfile .ID (),
122125 profile .ID () == "" && profile .Equals (pm .currentProfile ) && prefs .Equals (pm .prefs ):
123126 // The profile is already the current profile; no need to switch.
124127 //
@@ -176,7 +179,7 @@ func (pm *profileManager) DefaultUserProfile(uid ipn.WindowsUserID) ipn.LoginPro
176179 if err == ipn .ErrStateNotExist || len (b ) == 0 {
177180 if runtime .GOOS == "windows" {
178181 pm .dlogf ("DefaultUserProfile: windows: migrating from legacy preferences" )
179- profile , err := pm .migrateFromLegacyPrefs (uid , false )
182+ profile , err := pm .migrateFromLegacyPrefs (uid )
180183 if err == nil {
181184 return profile
182185 }
@@ -328,6 +331,23 @@ func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView, np ipn.NetworkProfile)
328331 delete (pm .knownProfiles , p .ID ())
329332 }
330333 }
334+ // TODO(nickkhyl): Revisit how we handle implicit switching to a different profile,
335+ // which occurs when prefsIn represents a node/user different from that of the
336+ // currentProfile. It happens when a login (either reauth or user-initiated login)
337+ // is completed with a different node/user identity than the one currently in use.
338+ //
339+ // Currently, we overwrite the existing profile prefs with the ones from prefsIn,
340+ // where prefsIn is the previous profile's prefs with an updated Persist, LoggedOut,
341+ // WantRunning and possibly other fields. This may not be the desired behavior.
342+ //
343+ // Additionally, LocalBackend doesn't treat it as a proper profile switch, meaning that
344+ // [LocalBackend.resetForProfileChangeLockedOnEntry] is not called and certain
345+ // node/profile-specific state may not be reset as expected.
346+ //
347+ // However, LocalBackend notifies [ipnext.Extension]s about the profile change,
348+ // so features migrated from LocalBackend to external packages should not be affected.
349+ //
350+ // See tailscale/corp#28014.
331351 pm .currentProfile = cp
332352 cp , err := pm .setProfilePrefs (nil , prefsIn , np )
333353 if err != nil {
@@ -746,28 +766,6 @@ func (pm *profileManager) NewProfileForUser(uid ipn.WindowsUserID) ipn.LoginProf
746766 return (& ipn.LoginProfile {LocalUserID : uid }).View ()
747767}
748768
749- // newProfileWithPrefs creates a new profile with the specified prefs and assigns
750- // the specified uid as the profile owner. If switchNow is true, it switches to the
751- // newly created profile immediately. It returns the newly created profile on success,
752- // or an error on failure.
753- func (pm * profileManager ) newProfileWithPrefs (uid ipn.WindowsUserID , prefs ipn.PrefsView , switchNow bool ) (ipn.LoginProfileView , error ) {
754- metricNewProfile .Add (1 )
755-
756- profile , err := pm .setProfilePrefs (& ipn.LoginProfile {LocalUserID : uid }, prefs , ipn.NetworkProfile {})
757- if err != nil {
758- return ipn.LoginProfileView {}, err
759- }
760- if switchNow {
761- pm .currentProfile = profile
762- pm .prefs = prefs .AsStruct ().View ()
763- pm .updateHealth ()
764- if err := pm .setProfileAsUserDefault (profile ); err != nil {
765- return ipn.LoginProfileView {}, err
766- }
767- }
768- return profile , nil
769- }
770-
771769// defaultPrefs is the default prefs for a new profile. This initializes before
772770// even this package's init() so do not rely on other parts of the system being
773771// fully initialized here (for example, syspolicy will not be available on
@@ -857,27 +855,9 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, ht *healt
857855 health : ht ,
858856 }
859857
858+ var initialProfile ipn.LoginProfileView
860859 if stateKey != "" {
861- for _ , v := range knownProfiles {
862- if v .Key () == stateKey {
863- pm .currentProfile = v
864- }
865- }
866- if ! pm .currentProfile .Valid () {
867- if suf , ok := strings .CutPrefix (string (stateKey ), "user-" ); ok {
868- pm .currentUserID = ipn .WindowsUserID (suf )
869- }
870- pm .SwitchToNewProfile ()
871- } else {
872- pm .currentUserID = pm .currentProfile .LocalUserID ()
873- }
874- prefs , err := pm .loadSavedPrefs (stateKey )
875- if err != nil {
876- return nil , err
877- }
878- if err := pm .setProfilePrefsNoPermCheck (pm .currentProfile , prefs ); err != nil {
879- return nil , err
880- }
860+ initialProfile = pm .findProfileByKey ("" , stateKey )
881861 // Most platform behavior is controlled by the goos parameter, however
882862 // some behavior is implied by build tag and fails when run on Windows,
883863 // so we explicitly avoid that behavior when running on Windows.
@@ -888,25 +868,32 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, ht *healt
888868 } else if len (knownProfiles ) == 0 && goos != "windows" && runtime .GOOS != "windows" {
889869 // No known profiles, try a migration.
890870 pm .dlogf ("no known profiles; trying to migrate from legacy prefs" )
891- if _ , err : = pm .migrateFromLegacyPrefs (pm .currentUserID , true ); err != nil {
892- return nil , err
871+ if initialProfile , err = pm .migrateFromLegacyPrefs (pm .currentUserID ); err != nil {
872+
893873 }
894- } else {
895- pm .SwitchToNewProfile ()
896874 }
897-
875+ if ! initialProfile .Valid () {
876+ var initialUserID ipn.WindowsUserID
877+ if suf , ok := strings .CutPrefix (string (stateKey ), "user-" ); ok {
878+ initialUserID = ipn .WindowsUserID (suf )
879+ }
880+ initialProfile = pm .NewProfileForUser (initialUserID )
881+ }
882+ if _ , _ , err := pm .SwitchToProfile (initialProfile ); err != nil {
883+ return nil , err
884+ }
898885 return pm , nil
899886}
900887
901- func (pm * profileManager ) migrateFromLegacyPrefs (uid ipn.WindowsUserID , switchNow bool ) (ipn.LoginProfileView , error ) {
888+ func (pm * profileManager ) migrateFromLegacyPrefs (uid ipn.WindowsUserID ) (ipn.LoginProfileView , error ) {
902889 metricMigration .Add (1 )
903890 sentinel , prefs , err := pm .loadLegacyPrefs (uid )
904891 if err != nil {
905892 metricMigrationError .Add (1 )
906893 return ipn.LoginProfileView {}, fmt .Errorf ("load legacy prefs: %w" , err )
907894 }
908895 pm .dlogf ("loaded legacy preferences; sentinel=%q" , sentinel )
909- profile , err := pm .newProfileWithPrefs ( uid , prefs , switchNow )
896+ profile , err := pm .setProfilePrefs ( & ipn. LoginProfile { LocalUserID : uid } , prefs , ipn. NetworkProfile {} )
910897 if err != nil {
911898 metricMigrationError .Add (1 )
912899 return ipn.LoginProfileView {}, fmt .Errorf ("migrating _daemon profile: %w" , err )
0 commit comments