Nosari20
Leverage Android Enterprise delegation scopes

Leverage Android Enterprise delegation scopes

Content

Delegation scopes

Android Enterprise delegation scopes define specific permissions that a Device Policy Pontroller (DPC) can delegate to other apps. These scopes allow other apps to perform defined management tasks such as certificate installation or app management without having full device admin privileges. Here are the available delegation scopes:

Examples

Wi-Fi On/Off button

A customer requested a solution that would allow users to enable or disable Wi-Fi while in kiosk mode, without giving them access to the full network settings. On Android, this is not possible through an app starting from Android 11. Therefore, we had to find an alternative approach. Using the newer method (i.e., showing the network chooser pop-up) requires the Settings app to be accessible in kiosk mode, which could also allow users to access all network-related settings.

As a solution, we used the DELEGATION_APP_RESTRICTIONS scope and created an app that sets the Wi-Fi state via the OEMConfig app restrictions. In this case, the customer was using Honeywell devices, so we applied Honneywell UEMConnect restrictions, as described below:

com.honeywell.oemconfig
.
└── network_configuration (bundle)
    └── wifi_settings (bundle)
        └── WifiEnable (string)

network_configuration.wifi_settings.WifiEnable

StateRestriction value
On3
Off1

On the app, when user clicks on the switch input, it reads the app restrictions sent by the MDM solution to UEMConnect, modify it with the values described above and apply it to the UEMConnect again (thanks to the delagation scope). After successfully making it work, we also added brightness, ring volume and rotation control.

Take bug report

Here is sample code to set app restrictions as explained:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

// Check if app has delagation for app restrictions
fun hasDelegation(context: Context): Boolean {
    val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
    val packageName = context.applicationContext.packageName
    val delegatedScopes = dpm.getDelegatedScopes(null, packageName)
    val hasDelegation =  delegatedScopes.contains(DevicePolicyManager.DELEGATION_APP_RESTRICTIONS)
    if(!hasDelegation){
        Toast.makeText(context, "Delegated scope DELEGATION_APP_RESTRICTIONS missing.", Toast.LENGTH_LONG).show()
    }
    return hasDelegation
}

// Set restrictions to the specified package
fun setRestrictions(context: Context, packageName: String, kvps:  Array<Pair<String, Any>>) {
    val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
    val restrictions: Bundle = dpm.getApplicationRestrictions(null, packageName).apply {
        for (kvp in kvps){
            Log.d("AppRestrictions", "Setting restriction: ${kvp.first} = ${kvp.second}")
            setNestedRestrictionValue(this, kvp.first, kvp.second)
        }
    }
    dpm.setApplicationRestrictions(null, packageName, restrictions)
}

// Update restriction Bundle
fun setNestedRestrictionValue(root: Bundle, keyPath: String, value: Any) {
    val keys = keyPath.split(".")
    var current = root

    for (i in 0 until keys.size - 1) {
        val key = keys[i]
        val next = current.getBundle(key)
        if (next == null) {
            val newBundle = Bundle()
            current.putBundle(key, newBundle)
            current = newBundle
        } else {
            current = next
        }
    }

    val finalKey = keys.last()
    when (value) {
        is Boolean -> current.putBoolean(finalKey, value)
        is Int -> current.putInt(finalKey, value)
        is String -> current.putString(finalKey, value)
        is Float -> current.putFloat(finalKey, value)
        is Long -> current.putLong(finalKey, value)
        else -> throw IllegalArgumentException("Unsupported type: ${value::class}")
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////

// Code can be used like this
if(hasDelegation(context)){
  setRestrictions(
      context, "com.honeywell.oemconfig", arrayOf(
          Pair("network_configuration.wifi_settings.WifiEnable", "3"),
      )
  )
}

Authentication certificate preselection

For my first time using delegated permissions, I was looking for a solution for a customer who asked whether it was possible to preselect a certificate to automate authentication against their IdP. Unfortunately, this isn’t possible, even with Chrome-managed restrictions.

We would be satisfied if, at the very least, we could prevent users from selecting the wrong certificate by preselecting the correct certificate alias while still prompting them to allow its usage by the app.

After some research, I found that this can be achieved using DELEGATION_CERT_SELECTION in combination with a DelegatedAdminReceiver.

You can find my fully functionnal app that leverage app config to setup mapping on Google Play : Managed Private Key Mapping

Take bug report

Here his sample code to do it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

class MyDelegatedAdminReceiver: DelegatedAdminReceiver() {

     override fun onChoosePrivateKeyAlias(
        context: Context,
        intent: Intent,
        uid: Int,
        rawUri: Uri?,
        alias: String?
    ): String? {


      val packageManager: PackageManager = context.packageManager
      val source = packageManager.getNameForUid(uid) // get package name of the app that request the certificate
      val uri = Uri.decode(rawUri.toString()).replace("/","")  // get the uri for which the certificate will be used


      if(source == "com.android.chrome" && uri == "example.com"){
        return "MY_CERT_ALIAS" // return cert alias without prompting and also allow app to use it
      }else{
        return null // user will be prompted to choose
      }

      // return KeyChain.KEY_ALIAS_SELECTION_DENIED if you want to deny access to keychain

    }

}

Sources / usefull resources