android: do not stop running on login, and edit prefs after startLogi… (#659)

android: do not stop running on login, and edit prefs after startLoginInteractive

Previously: start, edit prefs with wantRunning=false, then startLoginInteractive
Now: 1. editPrefs() with WantRunning=true, LoggedOut=false if AuthKey != null
     2. start() -> boots tailscaled
     3. startLoginInteractive()
Do not call wantRunning=false; the route clearing issue requiring that is resolved.

This also:
-add deepCopy function which copies MaskedPrefs. Note that .copy() does not copy the non-constructor parameters
-removes InternalExitNodePriorSet in MaskedPrefs, since this can't be set on the client

Updates tailscale/corp#24002

Signed-off-by: kari-ts <kari@tailscale.com>
pull/666/head
kari-ts 6 months ago committed by GitHub
parent 296b582520
commit 28084cbd27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -4,9 +4,9 @@
package com.tailscale.ipn.ui.model
import android.net.Uri
import java.util.UUID
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import java.util.UUID
class Ipn {
@ -95,11 +95,11 @@ class Ipn {
var ExitNodeIDSet: Boolean? = null,
var ExitNodeAllowLANAccessSet: Boolean? = null,
var WantRunningSet: Boolean? = null,
var LoggedOutSet: Boolean? = null,
var ShieldsUpSet: Boolean? = null,
var AdvertiseRoutesSet: Boolean? = null,
var ForceDaemonSet: Boolean? = null,
var HostnameSet: Boolean? = null,
var InternalExitNodePriorSet: Boolean? = null,
) {
var ControlURL: String? = null
@ -126,12 +126,6 @@ class Ipn {
ExitNodeIDSet = true
}
var InternalExitNodePrior: String? = null
set(value) {
field = value
InternalExitNodePriorSet = true
}
var ExitNodeAllowLANAccess: Boolean? = null
set(value) {
field = value
@ -144,6 +138,12 @@ class Ipn {
WantRunningSet = true
}
var LoggedOut: Boolean? = null
set(value) {
field = value
LoggedOutSet = true
}
var ShieldsUp: Boolean? = null
set(value) {
field = value
@ -238,3 +238,20 @@ class Persist {
var Provider: String = "",
)
}
fun Ipn.MaskedPrefs.deepCopy(): Ipn.MaskedPrefs {
return Ipn.MaskedPrefs().also {
if (this.ControlURLSet == true) it.ControlURL = this.ControlURL
if (this.RouteAllSet == true) it.RouteAll = this.RouteAll
if (this.CorpDNSSet == true) it.CorpDNS = this.CorpDNS
if (this.ExitNodeIDSet == true) it.ExitNodeID = this.ExitNodeID
if (this.ExitNodeAllowLANAccessSet == true)
it.ExitNodeAllowLANAccess = this.ExitNodeAllowLANAccess
if (this.WantRunningSet == true) it.WantRunning = this.WantRunning
if (this.LoggedOutSet == true) it.LoggedOut = this.LoggedOut
if (this.ShieldsUpSet == true) it.ShieldsUp = this.ShieldsUp
if (this.AdvertiseRoutesSet == true) it.AdvertiseRoutes = this.AdvertiseRoutes
if (this.ForceDaemonSet == true) it.ForceDaemon = this.ForceDaemon
if (this.HostnameSet == true) it.Hostname = this.Hostname
}
}

@ -11,6 +11,7 @@ import com.tailscale.ipn.ui.localapi.Client
import com.tailscale.ipn.ui.model.Ipn
import com.tailscale.ipn.ui.model.IpnLocal
import com.tailscale.ipn.ui.model.UserID
import com.tailscale.ipn.ui.model.deepCopy
import com.tailscale.ipn.ui.notifier.Notifier
import com.tailscale.ipn.ui.util.AdvertisedRoutesHelper
import com.tailscale.ipn.ui.util.LoadingIndicator
@ -144,52 +145,54 @@ open class IpnViewModel : ViewModel() {
// Login/Logout
/**
* Order of operations:
* 1. editPrefs() with maskedPrefs (to allow ControlURL override), WantRunning=true, LoggedOut=false if AuthKey != null
* 2. start() starts the LocalBackend state machine
* 3. startLoginInteractive() is currently required for bother interactive and non-interactive (using auth key) login
*
* Any failure shortcircuits the chain and invokes completionHandler once.
*/
fun login(
maskedPrefs: Ipn.MaskedPrefs? = null,
authKey: String? = null,
completionHandler: (Result<Unit>) -> Unit = {}
) {
val client = Client(viewModelScope)
val loginAction = {
Client(viewModelScope).startLoginInteractive { result ->
result
.onSuccess { TSLog.d(TAG, "Login started: $it") }
.onFailure { TSLog.e(TAG, "Error starting login: ${it.message}") }
completionHandler(result)
}
val finalMaskedPrefs = maskedPrefs?.deepCopy() ?: Ipn.MaskedPrefs()
finalMaskedPrefs.WantRunning = true
if (authKey != null) {
finalMaskedPrefs.LoggedOut = false
}
// Need to stop running before logging in to clear routes:
// https://linear.app/tailscale/issue/ENG-3441/routesdns-is-not-cleared-when-switching-profiles-or-reauthenticating
val stopThenLogin = {
Client(viewModelScope).editPrefs(Ipn.MaskedPrefs().apply { WantRunning = false }) { result ->
result
.onSuccess { loginAction() }
.onFailure { TSLog.e(TAG, "Error setting wantRunning to false: ${it.message}") }
client.editPrefs(finalMaskedPrefs) { editResult ->
editResult
.onFailure {
TSLog.e(TAG, "editPrefs() failed: ${it.message}")
completionHandler(Result.failure(it))
}
.onSuccess {
val opts = Ipn.Options(UpdatePrefs = editResult.getOrThrow(), AuthKey = authKey)
client.start(opts) { startResult ->
startResult
.onFailure {
TSLog.e(TAG, "start() failed: ${it.message}")
completionHandler(Result.failure(it))
}
.onSuccess {
client.startLoginInteractive { loginResult ->
loginResult
.onFailure {
TSLog.e(TAG, "startLoginInteractive() failed: ${it.message}")
completionHandler(Result.failure(it))
}
.onSuccess { completionHandler(Result.success(Unit)) }
}
val startAction = {
Client(viewModelScope).start(Ipn.Options(AuthKey = authKey)) { start ->
start.onFailure { completionHandler(Result.failure(it)) }.onSuccess { stopThenLogin() }
}
}
// If an MDM control URL is set, we will always use that in lieu of anything the user sets.
var prefs = maskedPrefs
val mdmControlURL = MDMSettings.loginURL.flow.value.value
if (mdmControlURL != null) {
prefs = prefs ?: Ipn.MaskedPrefs()
prefs.ControlURL = mdmControlURL
TSLog.d(TAG, "Overriding control URL with MDM value: $mdmControlURL")
}
prefs?.let {
Client(viewModelScope).editPrefs(it) { result ->
result.onFailure { completionHandler(Result.failure(it)) }.onSuccess { startAction() }
}
} ?: run { startAction() }
}
fun loginWithAuthKey(authKey: String, completionHandler: (Result<Unit>) -> Unit = {}) {

Loading…
Cancel
Save