From 1428c54e6c005394d8c622498baacfc2caf3bbc5 Mon Sep 17 00:00:00 2001 From: Andrea Gottardo Date: Sat, 13 Apr 2024 12:36:03 -0700 Subject: [PATCH] mdm: throw ErrNoSuchKey when a value not defined in Android syspolicy handler Fixes tailscale/tailscale#11716 The implementation of the syspolicy handler on Android was incomplete. As explained in the comments (https://github.com/tailscale/tailscale/blob/65f215115f0290502600d638557bf9b0f60aa6a6/util/syspolicy/handler.go#L27), a syspolicy handler should return a `syspolicy.ErrNoSuchKey` error when a value is not defined for a given key. The Android handler was instead returning an empty string. When attempting to log in with a custom coordination server, since we were not returning syspolicy.ErrNoSuchKey, the caller into the syspolicy package (https://github.com/tailscale/tailscale/blob/65f215115f0290502600d638557bf9b0f60aa6a6/ipn/prefs.go#L665) was always fetching an empty string from the MDM setting instead of using the default value `p.ControlURL`. Fallback logic would therefore always use the `DefaultControlURL` instead of the value defined in preferences. Verified that upon specifying a custom coordination server in the app UI, a login screen for that coordination server appears and I can connect the Android device to that custom coordination server. Signed-off-by: Andrea Gottardo --- android/src/main/java/com/tailscale/ipn/App.kt | 10 ++++++---- .../src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/App.kt b/android/src/main/java/com/tailscale/ipn/App.kt index ce20f33..7665ee7 100644 --- a/android/src/main/java/com/tailscale/ipn/App.kt +++ b/android/src/main/java/com/tailscale/ipn/App.kt @@ -453,17 +453,19 @@ class App : Application(), libtailscale.AppContext { return downloads } - @Throws(IOException::class, GeneralSecurityException::class) + @Throws( + IOException::class, GeneralSecurityException::class, MDMSettings.NoSuchKeyException::class) override fun getSyspolicyBooleanValue(key: String): Boolean { return getSyspolicyStringValue(key) == "true" } - @Throws(IOException::class, GeneralSecurityException::class) + @Throws( + IOException::class, GeneralSecurityException::class, MDMSettings.NoSuchKeyException::class) override fun getSyspolicyStringValue(key: String): String { return MDMSettings.allSettingsByKey[key]?.flow?.value?.toString() ?: run { - Log.d("MDM", "$key is not defined on Android. Returning empty.") - "" + Log.d("MDM", "$key is not defined on Android. Throwing NoSuchKeyException.") + throw MDMSettings.NoSuchKeyException() } } } diff --git a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt index 24364c0..d1d9223 100644 --- a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt +++ b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt @@ -11,6 +11,11 @@ import kotlin.reflect.full.isSubclassOf import kotlin.reflect.jvm.jvmErasure object MDMSettings { + // The String message used in this NoSuchKeyException must match the value of + // syspolicy.ErrNoSuchKey defined in Go, since the backend checks the value + // returned by the handler for equality using errors.Is(). + class NoSuchKeyException : Exception("no such key") + val forceEnabled = BooleanMDMSetting("ForceEnabled", "Force Enabled Connection Toggle") val exitNodeID = StringMDMSetting("ExitNodeID", "Forced Exit Node: Stable ID")