Максим Денисов, разработчик в Лиге Цифровой Экономики, поделился опытом создания системы управления доступом на Android и рассказал, как менялся подход к контролю доступа.
В этой статье расскажу, как изменился подход к контролю доступа к корпоративному устройству внутри одной компании.
Нашей задачей была разработка приложения, которое заблокирует доступ к потерянному устройству на Android, будет отправлять действия пользователя на сервер и обновляться также с сервера.
Такой опыт, на мой взгляд, будет интересен для тех, кто работает над подобными задачами, например системами управления корпоративной техникой или родительского контроля.
Разработка началась в 2015 году, когда 5-я версия Android была последней.
1. Подход на основе блокирующей view
1.1. Блокировка
Суть подхода — работа блокирующего окна, которое отображается поверх всех остальных. После авторизации оно закрывается.
В манифесте приложения нужно добавить следующее разрешение:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
С API 23 необходимо явно его указывать. Поэтому при настройке приложения администратор должен выбрать «Разрешать всегда».
У блокирующего экрана добавить свойства:
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
Кроме того, следует создать сервис, в котором ресивер отлавливает выключение экрана и вызывает блокирующую view.
private BroadcastReceiver screenStatusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
startLockScreenActivity(context);
}
}
public static void startLockScreenActivity(Context context) {
Intent activityIntent = new Intent(context, LockActivity.class);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activityIntent.addFlags(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
context.startActivity(activityIntent);
}
Чтобы пользователь не мог удалить приложение, последнему выдаются права администратора.
Intent activateDeviceAdmin = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
activateDeviceAdmin.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, policyManager.getAdminComponent());
activateDeviceAdmin.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "After activating admin, you will be able to block application uninstallation.");
startActivityForResult(activateDeviceAdmin, PolicyManager.DPM_ACTIVATION_REQUEST_CODE);
При попытке удалить права администратора происходит сброс к заводским настройкам.
devicePolicyManager.wipeData(0);
1.2. События безопасности
Для хранения событий пользователя применялась локальная база данных.
@Entity(tableName = "events")
data class Event(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
val id: Int,
@ColumnInfo(name = "login")
val login: String? = null,
……
Синхронизация происходила по таймеру.
internal inner class SyncEventTimer : TimerTask() {
override fun run() {
Timber.tag(TAG).d("SyncEventTimer")
GlobalScope.launch(Dispatchers.IO) {
try {
sendFromLocal()
} catch (e: Exception) {
Timber.tag(TAG).e(e)
}
}
}
}
1.3 Обновление
Обновление происходит через загрузку apk на устройство и вызов следующего интента:
Intent intent = new Intent( Intent.ACTION_INSTALL_PACKAGE
intent.setData( apkUri );
intent.setFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION );
context.startActivity( intent );
Со временем появились серьезные ограничения. В 9-й и 10-й версиях Android появилась возможность убрать блокирующую view, а также не удается блокировать запуск других приложений. Когда происходило обновление, у пользователя появлялась возможность закрыть блокировщик — он «снимался» в момент обновления.
Изначально было решено устанавливать еще одно приложение, которое бы и выполняло обновление, и выдавать ему права администратора. Однако это не помогло — у пользователя по-прежнему появлялась возможность закрыть блокировщик: основной экран появлялся во время обновления, и можно было совершить какие-либо действия с устройством.
Поэтому для новых версий Android такой подход, к сожалению, не применим.
2. Подход на основе библиотеки knox от Samsung
Дальнейшее развитие приложения потребовало контроль над запущенными приложениями. Для этой задачи использовалась библиотека Knox от Samsung. Помимо необходимой функции, библиотека упрощает работу с разрешениями и блокировкой устройства.
Для ее работы в манифесте добавляются разрешения:
<uses-permission android:name="com.samsung.android.knox.permission.KNOX_KIOSK_MODE" />
<uses-permission android:name="com.samsung.android.knox.permission.KNOX_CUSTOM_SYSTEM " />
<uses-permission android:name="com.samsung.android.knox.permission.KNOX_APP_MGMT" />
<uses-permission android:name="com.samsung.android.knox.permission.KNOX_ADVANCED_SECURITY" />
Для работы библиотеки необходимо ввести ключ лицензии.
val mSKL = KnoxEnterpriseLicenseManager.getInstance(context)
mSKL.activateLicense(“key”, context.packageName)
Выдача разрешений без запроса пользователю делалась так:
runtimePermissions.add("android.permission.WRITE_EXTERNAL_STORAGE");
Установка блокирующего приложения — следующим образом:
mKioskMode?.enableKioskMode(pkgName)
Отключение приложений на устройстве:
EnterpriseDeviceManager edm = EnterpriseDeviceManager.getInstance(context);
ApplicationPolicy appPolicy = edm.getApplicationPolicy();
appPolicy.setDisableApplication(“com.test.app”)
Проблема в том, что такой вариант работает только с техникой Samsung, а также нужно покупать ключ для использования библиотеки.
В нашем случае дальнейшее развитие потребовало поддержку устройств любых производителей. Поэтому мы перешли к подходу на основе владельца устройства.
3. Подход на основе владельца устройства
3.1. Установка режима device-owner
Для выдачи приложению администратора устройства необходимо выполнить команду через adb:
adb shell dpm set-device-owner ru.company.screenlocker/.AdminReceiver
3.2. Выдача разрешений
В этом режиме приложению можно выдать любые необходимые разрешения:
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val cn = ComponentName(context, AdminReceiver::class.java)
dpm.setPermissionGrantState(
cn, context.packageName,
Manifest.permission.REQUEST_INSTALL_PACKAGES,
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
)
3.3. Запрет на изменение приложения
Запретить пользователю менять приложение можно следующей командой:
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val cn = ComponentName(context, AdminReceiver::class.java)
dpm.addUserRestriction(cn, UserManager.DISALLOW_APPS_CONTROL)
3.4. Включить режим блокировки задач
В этом режиме пользователи устройств не могут видеть уведомления, получать доступ к приложениям, не включенным в белый список, или возвращаться на главный экран.
activity.startLockTask()
3.5. Установить свой рабочий стол
Получить список всех приложений с иконками:
val allApps = packageManager.queryIntentActivities(i, 0)
for (ri in allApps) {
val app = DesktopAppInfo()
app.label = ri.loadLabel(packageManager)
app.packageName = ri.activityInfo.packageName
app.icon = ri.activityInfo.loadIcon(packageManager)
loadList.add(app)
}
Соответственно, на своем рабочем столе можно разместить только нужные приложения.
На мой взгляд, это самый актуальный на сегодняшний день формат — работает на всех устройствах Android и на новых версиях ОС. Однако для установки приложения администратора необходимо форматировать устройство.
Приложение, которые мы разрабатывали, стало полноценным блокировщиком, который может останавливать другие аппы, отправлять события безопасности, обновляться и выводить список только разрешенных приложений на рабочий стол.
____________________________________________
В этом материале я описал изменение подхода к контролю корпоративной техники — от устаревшего к актуальному и на сегодняшний день. Имели ли вы дело с каким-либо их них?