diff --git a/android/src/main/java/com/tailscale/ipn/App.kt b/android/src/main/java/com/tailscale/ipn/App.kt index 7a9d564..ff933f6 100644 --- a/android/src/main/java/com/tailscale/ipn/App.kt +++ b/android/src/main/java/com/tailscale/ipn/App.kt @@ -45,6 +45,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import libtailscale.Libtailscale import java.io.File import java.io.IOException @@ -447,4 +449,16 @@ class App : Application(), libtailscale.AppContext { throw MDMSettings.NoSuchKeyException() } } + + @Throws( + IOException::class, GeneralSecurityException::class, MDMSettings.NoSuchKeyException::class) + override fun getSyspolicyStringArrayJSONValue(key: String): String { + val list = MDMSettings.allSettingsByKey[key]?.flow?.value as? List + try { + return Json.encodeToString(list) + } catch (e: Exception) { + 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 d1d9223..e8a470b 100644 --- a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt +++ b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt @@ -51,7 +51,8 @@ object MDMSettings { val runExitNode = ShowHideMDMSetting("RunExitNode", "Run as Exit Node") val testMenu = ShowHideMDMSetting("TestMenu", "Show Debug Menu") val updateMenu = ShowHideMDMSetting("UpdateMenu", "“Update Available” menu item") - + val allowedSuggestedExitNodes = + StringArrayListMDMSetting("AllowedSuggestedExitNodes", "Allowed Suggested Exit Nodes") val allSettings by lazy { MDMSettings::class .declaredMemberProperties diff --git a/libtailscale/interfaces.go b/libtailscale/interfaces.go index cc17561..d7f3227 100644 --- a/libtailscale/interfaces.go +++ b/libtailscale/interfaces.go @@ -51,6 +51,10 @@ type AppContext interface { // GetSyspolicyBooleanValue returns whether the given system policy is enabled. GetSyspolicyBooleanValue(key string) (bool, error) + + // GetSyspolicyStringArrayValue returns the current string array value for the given system policy, + // expressed as a JSON string. + GetSyspolicyStringArrayJSONValue(key string) (string, error) } // IPNService corresponds to our IPNService in Java. diff --git a/libtailscale/syspolicy_handler.go b/libtailscale/syspolicy_handler.go index b5ba3ad..8b795f7 100644 --- a/libtailscale/syspolicy_handler.go +++ b/libtailscale/syspolicy_handler.go @@ -4,6 +4,7 @@ package libtailscale import ( + "encoding/json" "errors" "log" @@ -46,3 +47,23 @@ func (h syspolicyHandler) ReadUInt64(key string) (uint64, error) { log.Fatalf("ReadUInt64 is not implemented on Android") return 0, nil } + +func (h syspolicyHandler) ReadStringArray(key string) ([]string, error) { + if key == "" { + return nil, syspolicy.ErrNoSuchKey + } + retVal, err := h.a.appCtx.GetSyspolicyStringArrayJSONValue(key) + if err != nil && !errors.Is(err, syspolicy.ErrNoSuchKey) { + log.Printf("syspolicy: failed to get string array value via gomobile: %v", err) + return nil, err + } + if retVal == "" { + return nil, syspolicy.ErrNoSuchKey + } + var arr []string + jsonErr := json.Unmarshal([]byte(retVal), &arr) + if jsonErr != nil { + return nil, jsonErr + } + return arr, err +}