@ -206,9 +206,7 @@ func (cc *mockControl) send(opts sendOpt) {
Err : err ,
}
if loginFinished {
s . SetStateForTest ( controlclient . StateAuthenticated )
} else if url == "" && err == nil && nm == nil {
s . SetStateForTest ( controlclient . StateNotAuthenticated )
s . LoggedIn = true
}
cc . opts . Observer . SetControlClientStatus ( cc , s )
}
@ -228,7 +226,6 @@ func (cc *mockControl) sendAuthURL(nm *netmap.NetworkMap) {
NetMap : nm ,
Persist : cc . persist . View ( ) ,
}
s . SetStateForTest ( controlclient . StateURLVisitRequired )
cc . opts . Observer . SetControlClientStatus ( cc , s )
}
@ -434,8 +431,11 @@ func runTestStateMachine(t *testing.T, seamless bool) {
// for it, so it doesn't count as Prefs.LoggedOut==true.
c . Assert ( prefs . LoggedOut ( ) , qt . IsTrue )
c . Assert ( prefs . WantRunning ( ) , qt . IsFalse )
c . Assert ( ipn . NeedsLogin , qt . Equals , * nn [ 1 ] . State )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify notification indicates we need login (prefs show logged out)
c . Assert ( nn [ 1 ] . Prefs == nil || nn [ 1 ] . Prefs . LoggedOut ( ) , qt . IsTrue )
// Verify the actual facts about our state
c . Assert ( needsLogin ( b ) , qt . IsTrue )
c . Assert ( hasValidNetMap ( b ) , qt . IsFalse )
}
// Restart the state machine.
@ -455,8 +455,11 @@ func runTestStateMachine(t *testing.T, seamless bool) {
c . Assert ( nn [ 1 ] . State , qt . IsNotNil )
c . Assert ( nn [ 0 ] . Prefs . LoggedOut ( ) , qt . IsTrue )
c . Assert ( nn [ 0 ] . Prefs . WantRunning ( ) , qt . IsFalse )
c . Assert ( ipn . NeedsLogin , qt . Equals , * nn [ 1 ] . State )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify notification indicates we need login
c . Assert ( nn [ 1 ] . Prefs == nil || nn [ 1 ] . Prefs . LoggedOut ( ) , qt . IsTrue )
// Verify the actual facts about our state
c . Assert ( needsLogin ( b ) , qt . IsTrue )
c . Assert ( hasValidNetMap ( b ) , qt . IsFalse )
}
// Start non-interactive login with no token.
@ -473,7 +476,8 @@ func runTestStateMachine(t *testing.T, seamless bool) {
// (This behaviour is needed so that b.Login() won't
// start connecting to an old account right away, if one
// exists when you launch another login.)
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify we still need login
c . Assert ( needsLogin ( b ) , qt . IsTrue )
}
// Attempted non-interactive login with no key; indicate that
@ -500,10 +504,11 @@ func runTestStateMachine(t *testing.T, seamless bool) {
c . Assert ( nn [ 1 ] . Prefs , qt . IsNotNil )
c . Assert ( nn [ 1 ] . Prefs . LoggedOut ( ) , qt . IsTrue )
c . Assert ( nn [ 1 ] . Prefs . WantRunning ( ) , qt . IsFalse )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify we need URL visit
c . Assert ( hasAuthURL ( b ) , qt . IsTrue )
c . Assert ( nn [ 2 ] . BrowseToURL , qt . IsNotNil )
c . Assert ( url1 , qt . Equals , * nn [ 2 ] . BrowseToURL )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
c . Assert ( isFullyAuthenticated ( b ) , qt . IsFalse )
}
// Now we'll try an interactive login.
@ -518,7 +523,8 @@ func runTestStateMachine(t *testing.T, seamless bool) {
cc . assertCalls ( )
c . Assert ( nn [ 0 ] . BrowseToURL , qt . IsNotNil )
c . Assert ( url1 , qt . Equals , * nn [ 0 ] . BrowseToURL )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify we still need to complete login
c . Assert ( needsLogin ( b ) , qt . IsTrue )
}
// Sometimes users press the Login button again, in the middle of
@ -534,7 +540,8 @@ func runTestStateMachine(t *testing.T, seamless bool) {
notifies . drain ( 0 )
// backend asks control for another login sequence
cc . assertCalls ( "Login" )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify we still need login
c . Assert ( needsLogin ( b ) , qt . IsTrue )
}
// Provide a new interactive login URL.
@ -550,7 +557,8 @@ func runTestStateMachine(t *testing.T, seamless bool) {
nn := notifies . drain ( 1 )
c . Assert ( nn [ 0 ] . BrowseToURL , qt . IsNotNil )
c . Assert ( url2 , qt . Equals , * nn [ 0 ] . BrowseToURL )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify we still need to complete login
c . Assert ( needsLogin ( b ) , qt . IsTrue )
}
// Pretend that the interactive login actually happened.
@ -582,10 +590,18 @@ func runTestStateMachine(t *testing.T, seamless bool) {
cc . assertCalls ( )
c . Assert ( nn [ 0 ] . LoginFinished , qt . IsNotNil )
c . Assert ( nn [ 1 ] . Prefs , qt . IsNotNil )
c . Assert ( nn [ 2 ] . State , qt . IsNotNil )
c . Assert ( nn [ 1 ] . Prefs . Persist ( ) . UserProfile ( ) . LoginName , qt . Equals , "user1" )
c . Assert ( ipn . NeedsMachineAuth , qt . Equals , * nn [ 2 ] . State )
c . Assert ( ipn . NeedsMachineAuth , qt . Equals , b . State ( ) )
// nn[2] is a state notification after login
// Verify login finished but need machine auth using backend state
c . Assert ( isFullyAuthenticated ( b ) , qt . IsTrue )
c . Assert ( needsMachineAuth ( b ) , qt . IsTrue )
nm := b . NetMap ( )
c . Assert ( nm , qt . IsNotNil )
// For an empty netmap (after initial login), SelfNode may not be valid yet.
// In this case, we can't check MachineAuthorized, but needsMachineAuth already verified the state.
if nm . SelfNode . Valid ( ) {
c . Assert ( nm . SelfNode . MachineAuthorized ( ) , qt . IsFalse )
}
}
// Pretend that the administrator has authorized our machine.
@ -603,8 +619,13 @@ func runTestStateMachine(t *testing.T, seamless bool) {
{
nn := notifies . drain ( 1 )
cc . assertCalls ( )
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
c . Assert ( ipn . Starting , qt . Equals , * nn [ 0 ] . State )
// nn[0] is a state notification after machine auth granted
c . Assert ( len ( nn ) , qt . Equals , 1 )
// Verify machine authorized using backend state
nm := b . NetMap ( )
c . Assert ( nm , qt . IsNotNil )
c . Assert ( nm . SelfNode . Valid ( ) , qt . IsTrue )
c . Assert ( nm . SelfNode . MachineAuthorized ( ) , qt . IsTrue )
}
// TODO: add a fake DERP server to our fake netmap, so we can
@ -627,9 +648,9 @@ func runTestStateMachine(t *testing.T, seamless bool) {
nn := notifies . drain ( 2 )
cc . assertCalls ( "pause" )
// BUG: I would expect Prefs to change first, and state after.
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
// nn[0] is state notification, nn[1] is prefs notification
c . Assert ( nn [ 1 ] . Prefs , qt . IsNotNil )
c . Assert ( ipn. Stopped , qt . Equals , * nn [ 0 ] . Stat e)
c . Assert ( nn[ 1 ] . Prefs . WantRunning ( ) , qt . IsFals e)
}
// The user changes their preference to WantRunning after all.
@ -645,17 +666,12 @@ func runTestStateMachine(t *testing.T, seamless bool) {
// BUG: Login isn't needed here. We never logged out.
cc . assertCalls ( "Login" , "unpause" )
// BUG: I would expect Prefs to change first, and state after.
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
// nn[0] is state notification, nn[1] is prefs notification
c . Assert ( nn [ 1 ] . Prefs , qt . IsNotNil )
c . Assert ( ipn. Starting , qt . Equals , * nn [ 0 ] . Stat e)
c . Assert ( nn[ 1 ] . Prefs . WantRunning ( ) , qt . IsTru e)
c . Assert ( store . sawWrite ( ) , qt . IsTrue )
}
// undo the state hack above.
b . mu . Lock ( )
b . state = ipn . Starting
b . mu . Unlock ( )
// User wants to logout.
store . awaitWrite ( )
t . Logf ( "\n\nLogout" )
@ -664,27 +680,26 @@ func runTestStateMachine(t *testing.T, seamless bool) {
{
nn := notifies . drain ( 5 )
previousCC . assertCalls ( "pause" , "Logout" , "unpause" , "Shutdown" )
// nn[0] is state notification (Stopped)
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
c . Assert ( * nn [ 0 ] . State , qt . Equals , ipn . Stopped )
// nn[1] is prefs notification after logout
c . Assert ( nn [ 1 ] . Prefs , qt . IsNotNil )
c . Assert ( nn [ 1 ] . Prefs . LoggedOut ( ) , qt . IsTrue )
c . Assert ( nn [ 1 ] . Prefs . WantRunning ( ) , qt . IsFalse )
cc . assertCalls ( "New" )
c . Assert ( nn [ 2 ] . State , qt . IsNotNil )
c . Assert ( * nn [ 2 ] . State , qt . Equals , ipn . NoState )
c . Assert ( nn [ 3 ] . Prefs , qt . IsNotNil ) // emptyPrefs
// nn[2] is the initial state notification after New (NoState)
// nn[3] is prefs notification with emptyPrefs
c . Assert ( nn [ 3 ] . Prefs , qt . IsNotNil )
c . Assert ( nn [ 3 ] . Prefs . LoggedOut ( ) , qt . IsTrue )
c . Assert ( nn [ 3 ] . Prefs . WantRunning ( ) , qt . IsFalse )
c . Assert ( nn [ 4 ] . State , qt . IsNotNil )
c . Assert ( * nn [ 4 ] . State , qt . Equals , ipn . NeedsLogin )
c . Assert ( b . State ( ) , qt . Equals , ipn . NeedsLogin )
c . Assert ( store . sawWrite ( ) , qt . IsTrue )
// nn[4] is state notification (NeedsLogin)
// Verify logged out and needs new login using backend state
c . Assert ( needsLogin ( b ) , qt . IsTrue )
c . Assert ( hasValidNetMap ( b ) , qt . IsFalse )
}
// A second logout should be a no-op as we are in the NeedsLogin state.
@ -696,7 +711,8 @@ func runTestStateMachine(t *testing.T, seamless bool) {
cc . assertCalls ( )
c . Assert ( b . Prefs ( ) . LoggedOut ( ) , qt . IsTrue )
c . Assert ( b . Prefs ( ) . WantRunning ( ) , qt . IsFalse )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify still needs login
c . Assert ( needsLogin ( b ) , qt . IsTrue )
}
// A third logout should also be a no-op as the cc should be in
@ -709,7 +725,8 @@ func runTestStateMachine(t *testing.T, seamless bool) {
cc . assertCalls ( )
c . Assert ( b . Prefs ( ) . LoggedOut ( ) , qt . IsTrue )
c . Assert ( b . Prefs ( ) . WantRunning ( ) , qt . IsFalse )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify still needs login
c . Assert ( needsLogin ( b ) , qt . IsTrue )
}
// Oh, you thought we were done? Ha! Now we have to test what
@ -732,11 +749,13 @@ func runTestStateMachine(t *testing.T, seamless bool) {
nn := notifies . drain ( 2 )
cc . assertCalls ( )
c . Assert ( nn [ 0 ] . Prefs , qt . IsNotNil )
c . Assert ( nn [ 1 ] . State , qt . IsNotNil )
c . Assert ( nn [ 0 ] . Prefs . LoggedOut ( ) , qt . IsTrue )
c . Assert ( nn [ 0 ] . Prefs . WantRunning ( ) , qt . IsFalse )
c . Assert ( ipn . NeedsLogin , qt . Equals , * nn [ 1 ] . State )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// Verify notification indicates we need login
c . Assert ( nn [ 1 ] . Prefs == nil || nn [ 1 ] . Prefs . LoggedOut ( ) , qt . IsTrue )
// Verify we need login after restart
c . Assert ( needsLogin ( b ) , qt . IsTrue )
c . Assert ( hasValidNetMap ( b ) , qt . IsFalse )
}
// Explicitly set the ControlURL to avoid defaulting to [ipn.DefaultControlURL].
@ -787,8 +806,9 @@ func runTestStateMachine(t *testing.T, seamless bool) {
c . Assert ( nn [ 1 ] . Prefs . LoggedOut ( ) , qt . IsFalse )
// If a user initiates an interactive login, they also expect WantRunning to become true.
c . Assert ( nn [ 1 ] . Prefs . WantRunning ( ) , qt . IsTrue )
c . Assert ( nn [ 2 ] . State , qt . IsNotNil )
c . Assert ( ipn . Starting , qt . Equals , * nn [ 2 ] . State )
// nn[2] is state notification (Starting) - verify using backend state
c . Assert ( isWantRunning ( b ) , qt . IsTrue )
c . Assert ( isLoggedIn ( b ) , qt . IsTrue )
}
// Now we've logged in successfully. Let's disconnect.
@ -802,9 +822,9 @@ func runTestStateMachine(t *testing.T, seamless bool) {
nn := notifies . drain ( 2 )
cc . assertCalls ( "pause" )
// BUG: I would expect Prefs to change first, and state after.
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
// nn[0] is state notification (Stopped), nn[1] is prefs notification
c . Assert ( nn [ 1 ] . Prefs , qt . IsNotNil )
c . Assert ( ipn. Stopped , qt . Equals , * nn [ 0 ] . Stat e)
c . Assert ( nn[ 1 ] . Prefs . WantRunning ( ) , qt . IsFals e)
c . Assert ( nn [ 1 ] . Prefs . LoggedOut ( ) , qt . IsFalse )
}
@ -822,10 +842,11 @@ func runTestStateMachine(t *testing.T, seamless bool) {
// and WantRunning is false, so cc should be paused.
cc . assertCalls ( "New" , "Login" , "pause" )
c . Assert ( nn [ 0 ] . Prefs , qt . IsNotNil )
c . Assert ( nn [ 1 ] . State , qt . IsNotNil )
c . Assert ( nn [ 0 ] . Prefs . WantRunning ( ) , qt . IsFalse )
c . Assert ( nn [ 0 ] . Prefs . LoggedOut ( ) , qt . IsFalse )
c . Assert ( * nn [ 1 ] . State , qt . Equals , ipn . Stopped )
// nn[1] is state notification (Stopped)
// Verify backend shows we're not wanting to run
c . Assert ( isWantRunning ( b ) , qt . IsFalse )
}
// When logged in but !WantRunning, ipn leaves us unpaused to retrieve
@ -863,9 +884,9 @@ func runTestStateMachine(t *testing.T, seamless bool) {
nn := notifies . drain ( 2 )
cc . assertCalls ( "Login" , "unpause" )
// BUG: I would expect Prefs to change first, and state after.
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
// nn[0] is state notification (Starting), nn[1] is prefs notification
c . Assert ( nn [ 1 ] . Prefs , qt . IsNotNil )
c . Assert ( ipn. Starting , qt . Equals , * nn [ 0 ] . Stat e)
c . Assert ( nn[ 1 ] . Prefs . WantRunning ( ) , qt . IsTru e)
}
// Disconnect.
@ -879,9 +900,9 @@ func runTestStateMachine(t *testing.T, seamless bool) {
nn := notifies . drain ( 2 )
cc . assertCalls ( "pause" )
// BUG: I would expect Prefs to change first, and state after.
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
// nn[0] is state notification (Stopped), nn[1] is prefs notification
c . Assert ( nn [ 1 ] . Prefs , qt . IsNotNil )
c . Assert ( ipn. Stopped , qt . Equals , * nn [ 0 ] . Stat e)
c . Assert ( nn[ 1 ] . Prefs . WantRunning ( ) , qt . IsFals e)
}
// We want to try logging in as a different user, while Stopped.
@ -926,12 +947,13 @@ func runTestStateMachine(t *testing.T, seamless bool) {
cc . assertCalls ( "unpause" )
c . Assert ( nn [ 0 ] . LoginFinished , qt . IsNotNil )
c . Assert ( nn [ 1 ] . Prefs , qt . IsNotNil )
c . Assert ( nn [ 2 ] . State , qt . IsNotNil )
// Prefs after finishing the login, so LoginName updated.
c . Assert ( nn [ 1 ] . Prefs . Persist ( ) . UserProfile ( ) . LoginName , qt . Equals , "user3" )
c . Assert ( nn [ 1 ] . Prefs . LoggedOut ( ) , qt . IsFalse )
c . Assert ( nn [ 1 ] . Prefs . WantRunning ( ) , qt . IsTrue )
c . Assert ( ipn . Starting , qt . Equals , * nn [ 2 ] . State )
// nn[2] is state notification (Starting) - verify using backend state
c . Assert ( isWantRunning ( b ) , qt . IsTrue )
c . Assert ( isLoggedIn ( b ) , qt . IsTrue )
}
// The last test case is the most common one: restarting when both
@ -950,11 +972,10 @@ func runTestStateMachine(t *testing.T, seamless bool) {
c . Assert ( nn [ 0 ] . Prefs , qt . IsNotNil )
c . Assert ( nn [ 0 ] . Prefs . LoggedOut ( ) , qt . IsFalse )
c . Assert ( nn [ 0 ] . Prefs . WantRunning ( ) , qt . IsTrue )
// We're logged in and have a valid netmap, so we should
// be in the Starting state.
c . Assert ( nn [ 1 ] . State , qt . IsNotNil )
c . Assert ( * nn [ 1 ] . State , qt . Equals , ipn . Starting )
c . Assert ( b . State ( ) , qt . Equals , ipn . Starting )
// nn[1] is state notification (Starting)
// Verify we're authenticated with valid netmap using backend state
c . Assert ( isFullyAuthenticated ( b ) , qt . IsTrue )
c . Assert ( hasValidNetMap ( b ) , qt . IsTrue )
}
// Control server accepts our valid key from before.
@ -971,7 +992,9 @@ func runTestStateMachine(t *testing.T, seamless bool) {
// NOTE: No prefs change this time. WantRunning stays true.
// We were in Starting in the first place, so that doesn't
// change either, so we don't expect any notifications.
c . Assert ( ipn . Starting , qt . Equals , b . State ( ) )
// Verify we're still authenticated with valid netmap
c . Assert ( isFullyAuthenticated ( b ) , qt . IsTrue )
c . Assert ( hasValidNetMap ( b ) , qt . IsTrue )
}
t . Logf ( "\n\nExpireKey" )
notifies . expect ( 1 )
@ -982,9 +1005,10 @@ func runTestStateMachine(t *testing.T, seamless bool) {
{
nn := notifies . drain ( 1 )
cc . assertCalls ( )
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
c . Assert ( ipn . NeedsLogin , qt . Equals , * nn [ 0 ] . State )
c . Assert ( ipn . NeedsLogin , qt . Equals , b . State ( ) )
// nn[0] is state notification (NeedsLogin) due to key expiry
c . Assert ( len ( nn ) , qt . Equals , 1 )
// Verify key expired, need new login using backend state
c . Assert ( needsLogin ( b ) , qt . IsTrue )
c . Assert ( b . isEngineBlocked ( ) , qt . IsTrue )
}
@ -997,9 +1021,11 @@ func runTestStateMachine(t *testing.T, seamless bool) {
{
nn := notifies . drain ( 1 )
cc . assertCalls ( )
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
c . Assert ( ipn . Starting , qt . Equals , * nn [ 0 ] . State )
c . Assert ( ipn . Starting , qt . Equals , b . State ( ) )
// nn[0] is state notification (Starting) after key extension
c . Assert ( len ( nn ) , qt . Equals , 1 )
// Verify key extended, authenticated again using backend state
c . Assert ( isFullyAuthenticated ( b ) , qt . IsTrue )
c . Assert ( hasValidNetMap ( b ) , qt . IsTrue )
c . Assert ( b . isEngineBlocked ( ) , qt . IsFalse )
}
notifies . expect ( 1 )
@ -1008,9 +1034,10 @@ func runTestStateMachine(t *testing.T, seamless bool) {
{
nn := notifies . drain ( 1 )
cc . assertCalls ( )
c . Assert ( nn [ 0 ] . State , qt . IsNotNil )
c . Assert ( ipn . Running , qt . Equals , * nn [ 0 ] . State )
c . Assert ( ipn . Running , qt . Equals , b . State ( ) )
// nn[0] is state notification (Running) after DERP connection
c . Assert ( len ( nn ) , qt . Equals , 1 )
// Verify we can route traffic using backend state
c . Assert ( canRouteTraffic ( b ) , qt . IsTrue )
}
}
@ -1901,3 +1928,77 @@ func (e *mockEngine) Close() {
func ( e * mockEngine ) Done ( ) <- chan struct { } {
return e . done
}
// hasValidNetMap returns true if the backend has a valid network map with a valid self node.
func hasValidNetMap ( b * LocalBackend ) bool {
nm := b . NetMap ( )
return nm != nil && nm . SelfNode . Valid ( )
}
// needsLogin returns true if the backend needs user login action.
// This is true when logged out, when an auth URL is present (interactive login in progress),
// or when the node key has expired.
func needsLogin ( b * LocalBackend ) bool {
// Note: b.Prefs() handles its own locking, so we lock only for authURL and keyExpired access
b . mu . Lock ( )
authURL := b . authURL
keyExpired := b . keyExpired
b . mu . Unlock ( )
return b . Prefs ( ) . LoggedOut ( ) || authURL != "" || keyExpired
}
// needsMachineAuth returns true if the user has logged in but the machine is not yet authorized.
// This includes the case where we have a netmap but no valid SelfNode yet (empty netmap after initial login).
func needsMachineAuth ( b * LocalBackend ) bool {
// Note: b.NetMap() and b.Prefs() handle their own locking
nm := b . NetMap ( )
prefs := b . Prefs ( )
if prefs . LoggedOut ( ) || nm == nil {
return false
}
// If we have a valid SelfNode, check its MachineAuthorized status
if nm . SelfNode . Valid ( ) {
return ! nm . SelfNode . MachineAuthorized ( )
}
// Empty netmap (no SelfNode yet) after login also means we need machine auth
return true
}
// hasAuthURL returns true if an authentication URL is present (user needs to visit a URL).
func hasAuthURL ( b * LocalBackend ) bool {
b . mu . Lock ( )
authURL := b . authURL
b . mu . Unlock ( )
return authURL != ""
}
// canRouteTraffic returns true if the backend is capable of routing traffic.
// This requires a valid netmap, machine authorization, and WantRunning preference.
func canRouteTraffic ( b * LocalBackend ) bool {
// Note: b.NetMap() and b.Prefs() handle their own locking
nm := b . NetMap ( )
prefs := b . Prefs ( )
return nm != nil &&
nm . SelfNode . Valid ( ) &&
nm . SelfNode . MachineAuthorized ( ) &&
prefs . WantRunning ( )
}
// isFullyAuthenticated returns true if the user has completed login and no auth URL is pending.
func isFullyAuthenticated ( b * LocalBackend ) bool {
// Note: b.Prefs() handles its own locking, so we lock only for authURL access
b . mu . Lock ( )
authURL := b . authURL
b . mu . Unlock ( )
return ! b . Prefs ( ) . LoggedOut ( ) && authURL == ""
}
// isWantRunning returns true if the WantRunning preference is set.
func isWantRunning ( b * LocalBackend ) bool {
return b . Prefs ( ) . WantRunning ( )
}
// isLoggedIn returns true if the user is logged in (not logged out).
func isLoggedIn ( b * LocalBackend ) bool {
return ! b . Prefs ( ) . LoggedOut ( )
}