Skip to content

Commit

Permalink
ui: port syspolicy handler code to new app
Browse files Browse the repository at this point in the history
port over #199 from cmd/tailscale and legacy_android to libtailscale and android/

Updates tailscale/corp#18202

Signed-off-by: kari-ts <kari@tailscale.com>
  • Loading branch information
kari-ts committed Apr 10, 2024
1 parent 38f57b4 commit 97b40ef
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 7 deletions.
33 changes: 33 additions & 0 deletions android/src/main/java/com/tailscale/ipn/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.RestrictionsManager
import android.content.SharedPreferences
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
Expand All @@ -37,6 +38,10 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.tailscale.ipn.mdm.AlwaysNeverUserDecidesMDMSetting
import com.tailscale.ipn.mdm.BooleanMDMSetting
import com.tailscale.ipn.mdm.ShowHideMDMSetting
import com.tailscale.ipn.mdm.StringMDMSetting
import com.tailscale.ipn.ui.localapi.Client
import com.tailscale.ipn.ui.localapi.Request
import com.tailscale.ipn.ui.model.Ipn
Expand Down Expand Up @@ -451,4 +456,32 @@ class App : Application(), libtailscale.AppContext {

return downloads
}

@Throws(IOException::class, GeneralSecurityException::class)
override fun getSyspolicyBooleanValue(key: String): Boolean {
val manager = getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager
return BooleanMDMSetting(key, key).getFrom(manager.applicationRestrictions, this)
}

@Throws(IOException::class, GeneralSecurityException::class)
override fun getSyspolicyStringValue(key: String): String {
val manager = getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager

val alwaysNeverSetting = AlwaysNeverUserDecidesMDMSetting(key, key, false)
val showHideSetting = ShowHideMDMSetting(key, key, false)
val stringSetting = StringMDMSetting(key, key, false)

return when {
alwaysNeverSetting.keyExists(key) ->
alwaysNeverSetting.getFrom(manager.applicationRestrictions, this).value
showHideSetting.keyExists(key) ->
showHideSetting.getFrom(manager.applicationRestrictions, this).value
stringSetting.keyExists(key) ->
stringSetting.getFrom(manager.applicationRestrictions, this) ?: ""
else -> {
Log.d("MDM", "$key is not defined on Android. Returning empty.")
""
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,32 @@
package com.tailscale.ipn.mdm

import android.os.Bundle
import android.util.Log
import com.tailscale.ipn.App
import com.tailscale.ipn.ui.util.set
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

abstract class MDMSetting<T>(defaultValue: T, val key: String, val localizedTitle: String) {
// Don't keep track of the key if shouldRegisterKey is false - shouldRegisterKey should be false when creating an MDMSetting to check whether a key has been registered with that setting.
abstract class MDMSetting<T>(defaultValue: T, val key: String, val localizedTitle: String, private val shouldRegisterKey: Boolean = true) {
private val keys = mutableSetOf<String>()

init {
if (shouldRegisterKey) { registerKey(key)
}
}
val flow: StateFlow<T> = MutableStateFlow<T>(defaultValue)

private fun registerKey(key: String) {
if (!keyExists(key)) {
keys.add(key)
}
}

fun keyExists(key: String): Boolean {
return keys.contains(key)
}

fun setFrom(bundle: Bundle?, app: App) {
val v = getFrom(bundle, app)
flow.set(v)
Expand All @@ -26,8 +44,8 @@ class BooleanMDMSetting(key: String, localizedTitle: String) :
bundle?.getBoolean(key) ?: app.getEncryptedPrefs().getBoolean(key, false)
}

class StringMDMSetting(key: String, localizedTitle: String) :
MDMSetting<String?>(null, key, localizedTitle) {
class StringMDMSetting(key: String, localizedTitle: String, shouldRegisterKey: Boolean = true) :
MDMSetting<String?>(null, key, localizedTitle, shouldRegisterKey) {
override fun getFrom(bundle: Bundle?, app: App) =
bundle?.getString(key) ?: app.getEncryptedPrefs().getString(key, null)
}
Expand All @@ -39,8 +57,8 @@ class StringArrayListMDMSetting(key: String, localizedTitle: String) :
?: app.getEncryptedPrefs().getStringSet(key, HashSet<String>())?.toList()
}

class AlwaysNeverUserDecidesMDMSetting(key: String, localizedTitle: String) :
MDMSetting<AlwaysNeverUserDecides>(AlwaysNeverUserDecides.UserDecides, key, localizedTitle) {
class AlwaysNeverUserDecidesMDMSetting(key: String, localizedTitle: String, shouldRegisterKey: Boolean = true) :
MDMSetting<AlwaysNeverUserDecides>(AlwaysNeverUserDecides.UserDecides, key, localizedTitle, shouldRegisterKey) {
override fun getFrom(bundle: Bundle?, app: App): AlwaysNeverUserDecides {
val storedString =
bundle?.getString(key)
Expand All @@ -60,8 +78,8 @@ class AlwaysNeverUserDecidesMDMSetting(key: String, localizedTitle: String) :
}
}

class ShowHideMDMSetting(key: String, localizedTitle: String) :
MDMSetting<ShowHide>(ShowHide.Show, key, localizedTitle) {
class ShowHideMDMSetting(key: String, localizedTitle: String, shouldRegisterKey: Boolean = true) :
MDMSetting<ShowHide>(ShowHide.Show, key, localizedTitle, shouldRegisterKey) {
override fun getFrom(bundle: Bundle?, app: App): ShowHide {
val storedString =
bundle?.getString(key)
Expand Down
6 changes: 6 additions & 0 deletions libtailscale/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ type AppContext interface {
// GetPlatformDNSConfig gets a string representation of the current DNS
// configuration.
GetPlatformDNSConfig() string

// GetSyspolicyStringValue returns the current string value for the given system policy.
GetSyspolicyStringValue(key string) (string, error)

// GetSyspolicyBooleanValue returns whether the given system policy is enabled.
GetSyspolicyBooleanValue(key string) (bool, error)
}

// IPNService corresponds to our IPNService in Java.
Expand Down
47 changes: 47 additions & 0 deletions libtailscale/syspolicy_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

package libtailscale

import (
"log"

"tailscale.com/util/syspolicy"
)

// androidHandler is a syspolicy handler for the Android version of the Tailscale client,
// which lets the main networking code read values set via the Android RestrictionsManager.
type androidHandler struct {
a *App
}

func (h androidHandler) ReadString(key string) (string, error) {
if key == "" {
return "", syspolicy.ErrNoSuchKey
}
retVal, err := h.a.appCtx.GetSyspolicyStringValue(key)
if err != nil {
log.Printf("syspolicy: failed to get string value via gomobile: %v", err)
}
return retVal, err
}

func (h androidHandler) ReadBoolean(key string) (bool, error) {
if key == "" {
return false, syspolicy.ErrNoSuchKey
}
retVal, err := h.a.appCtx.GetSyspolicyBooleanValue(key)
if err != nil {
log.Printf("syspolicy: failed to get bool value via gomobile: %v", err)
}
return retVal, err
}

func (h androidHandler) ReadUInt64(key string) (uint64, error) {
if key == "" {
return 0, syspolicy.ErrNoSuchKey
}
// TODO(angott): drop ReadUInt64 everywhere. We are not using it.
log.Fatalf("ReadUInt64 is not implemented on Android")
return 0, nil
}
2 changes: 2 additions & 0 deletions libtailscale/tailscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/util/clientmetric"
"tailscale.com/util/syspolicy"
)

const defaultMTU = 1280 // minimalMTU from wgengine/userspace.go
Expand All @@ -38,6 +39,7 @@ func newApp(dataDir, directFileRoot string, appCtx AppContext) Application {

a.store = newStateStore(a.appCtx)
interfaces.RegisterInterfaceGetter(a.getInterfaces)
syspolicy.RegisterHandler(androidHandler{a: a})
go func() {
defer func() {
if p := recover(); p != nil {
Expand Down

0 comments on commit 97b40ef

Please sign in to comment.