Merge pull request #14061 from nextcloud/mdm-enforce-protection

Feature - MDM Enforce App Protection
This commit is contained in:
Tobias Kaminsky 2024-11-25 08:45:07 +01:00 committed by GitHub
commit 72fdb340eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 116 additions and 22 deletions

View file

@ -119,6 +119,9 @@ object MDMConfig {
fun getPort(context: Context): Int = fun getPort(context: Context): Int =
context.getRestriction(AppConfigKeys.ProxyPort, context.resources.getInteger(R.integer.proxy_port)) context.getRestriction(AppConfigKeys.ProxyPort, context.resources.getInteger(R.integer.proxy_port))
fun enforceProtection(context: Context): Boolean =
context.getRestriction(AppConfigKeys.EnforceProtection, context.resources.getBoolean(R.bool.enforce_protection))
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
private fun <T : Any> Context.getRestriction(appConfigKey: AppConfigKeys, defaultValue: T): T { private fun <T : Any> Context.getRestriction(appConfigKey: AppConfigKeys, defaultValue: T): T {
val restrictionsManager = getSystemService(Context.RESTRICTIONS_SERVICE) as? RestrictionsManager val restrictionsManager = getSystemService(Context.RESTRICTIONS_SERVICE) as? RestrictionsManager

View file

@ -17,11 +17,13 @@ import android.view.View
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.utils.mdm.MDMConfig
import com.owncloud.android.R import com.owncloud.android.R
import com.owncloud.android.authentication.AuthenticatorActivity import com.owncloud.android.authentication.AuthenticatorActivity
import com.owncloud.android.databinding.ActivitySplashBinding import com.owncloud.android.databinding.ActivitySplashBinding
import com.owncloud.android.ui.activity.BaseActivity import com.owncloud.android.ui.activity.BaseActivity
import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.activity.SettingsActivity
import javax.inject.Inject import javax.inject.Inject
class LauncherActivity : BaseActivity() { class LauncherActivity : BaseActivity() {
@ -65,7 +67,11 @@ class LauncherActivity : BaseActivity() {
private fun scheduleSplashScreen() { private fun scheduleSplashScreen() {
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
if (user.isPresent) { if (user.isPresent) {
startActivity(Intent(this, FileDisplayActivity::class.java)) if (MDMConfig.enforceProtection(this) && appPreferences.lockPreference == SettingsActivity.LOCK_NONE) {
startActivity(Intent(this, SettingsActivity::class.java))
} else {
startActivity(Intent(this, FileDisplayActivity::class.java))
}
} else { } else {
startActivity(Intent(this, AuthenticatorActivity::class.java)) startActivity(Intent(this, AuthenticatorActivity::class.java))
} }

View file

@ -105,6 +105,7 @@ import com.owncloud.android.services.OperationsService;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder; import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
import com.owncloud.android.ui.NextcloudWebViewClient; import com.owncloud.android.ui.NextcloudWebViewClient;
import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.activity.SettingsActivity;
import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener; import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
@ -123,6 +124,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -1361,11 +1363,17 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
private void endSuccess() { private void endSuccess() {
if (!onlyAdd) { if (!onlyAdd) {
Intent i = new Intent(this, FileDisplayActivity.class); if (MDMConfig.INSTANCE.enforceProtection(this) && Objects.equals(preferences.getLockPreference(), SettingsActivity.LOCK_NONE)) {
i.setAction(FileDisplayActivity.RESTART); Intent i = new Intent(this, SettingsActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(i);
startActivity(i); } else {
Intent i = new Intent(this, FileDisplayActivity.class);
i.setAction(FileDisplayActivity.RESTART);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
} }
finish(); finish();
} }

View file

@ -0,0 +1,46 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.preference.ListPreference
import android.util.AttributeSet
import com.nextcloud.utils.extensions.setVisibleIf
@Suppress("DEPRECATION")
class ListPreferenceDialog(context: Context?, attrs: AttributeSet?) : ListPreference(context, attrs) {
fun showDialog() {
if (!isDialogCreated()) {
onClick()
}
}
fun dismissible(value: Boolean) {
if (isDialogCreated()) {
dialog.setCancelable(value)
dialog.setCanceledOnTouchOutside(value)
}
}
fun enableCancelButton(value: Boolean) {
if (isDialogCreated()) {
(dialog as? AlertDialog)?.let {
val cancelButton = it.getButton(Dialog.BUTTON_NEGATIVE)
cancelButton?.setVisibleIf(value)
cancelButton?.isEnabled = value
}
}
}
private fun isDialogCreated(): Boolean {
return dialog != null
}
}

View file

@ -61,6 +61,7 @@ import com.owncloud.android.lib.common.ExternalLink;
import com.owncloud.android.lib.common.ExternalLinkType; import com.owncloud.android.lib.common.ExternalLinkType;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.providers.DocumentsStorageProvider; import com.owncloud.android.providers.DocumentsStorageProvider;
import com.owncloud.android.ui.ListPreferenceDialog;
import com.owncloud.android.ui.ThemeableSwitchPreference; import com.owncloud.android.ui.ThemeableSwitchPreference;
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask; import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment; import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
@ -75,6 +76,7 @@ import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import javax.inject.Inject; import javax.inject.Inject;
@ -122,7 +124,7 @@ public class SettingsActivity extends PreferenceActivity
private Uri serverBaseUri; private Uri serverBaseUri;
private ListPreference lock; private ListPreferenceDialog lock;
private ThemeableSwitchPreference showHiddenFiles; private ThemeableSwitchPreference showHiddenFiles;
private ThemeableSwitchPreference showEcosystemApps; private ThemeableSwitchPreference showEcosystemApps;
private AppCompatDelegate delegate; private AppCompatDelegate delegate;
@ -139,7 +141,6 @@ public class SettingsActivity extends PreferenceActivity
@Inject ViewThemeUtils viewThemeUtils; @Inject ViewThemeUtils viewThemeUtils;
@Inject ConnectivityService connectivityService; @Inject ConnectivityService connectivityService;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -185,6 +186,15 @@ public class SettingsActivity extends PreferenceActivity
// workaround for mismatched color when app dark mode and system dark mode don't agree // workaround for mismatched color when app dark mode and system dark mode don't agree
setListBackground(); setListBackground();
showPasscodeDialogIfEnforceAppProtection();
}
private void showPasscodeDialogIfEnforceAppProtection() {
if (MDMConfig.INSTANCE.enforceProtection(this) && Objects.equals(preferences.getLockPreference(), SettingsActivity.LOCK_NONE) && lock != null) {
lock.showDialog();
lock.dismissible(false);
lock.enableCancelButton(false);
}
} }
private void setupDevCategory(PreferenceScreen preferenceScreen) { private void setupDevCategory(PreferenceScreen preferenceScreen) {
@ -678,26 +688,35 @@ public class SettingsActivity extends PreferenceActivity
private void setupLockPreference(PreferenceCategory preferenceCategoryDetails, private void setupLockPreference(PreferenceCategory preferenceCategoryDetails,
boolean passCodeEnabled, boolean passCodeEnabled,
boolean deviceCredentialsEnabled) { boolean deviceCredentialsEnabled) {
lock = (ListPreference) findPreference(PREFERENCE_LOCK); boolean enforceProtection = MDMConfig.INSTANCE.enforceProtection(this);
lock = (ListPreferenceDialog) findPreference(PREFERENCE_LOCK);
int optionSize = 3;
if (enforceProtection) {
optionSize = 2;
}
if (lock != null && (passCodeEnabled || deviceCredentialsEnabled)) { if (lock != null && (passCodeEnabled || deviceCredentialsEnabled)) {
ArrayList<String> lockEntries = new ArrayList<>(3); ArrayList<String> lockEntries = new ArrayList<>(optionSize);
lockEntries.add(getString(R.string.prefs_lock_none));
lockEntries.add(getString(R.string.prefs_lock_using_passcode)); lockEntries.add(getString(R.string.prefs_lock_using_passcode));
lockEntries.add(getString(R.string.prefs_lock_using_device_credentials)); lockEntries.add(getString(R.string.prefs_lock_using_device_credentials));
ArrayList<String> lockValues = new ArrayList<>(3); ArrayList<String> lockValues = new ArrayList<>(optionSize);
lockValues.add(LOCK_NONE);
lockValues.add(LOCK_PASSCODE); lockValues.add(LOCK_PASSCODE);
lockValues.add(LOCK_DEVICE_CREDENTIALS); lockValues.add(LOCK_DEVICE_CREDENTIALS);
if (!passCodeEnabled) { if (!enforceProtection) {
lockEntries.remove(1); lockEntries.add(getString(R.string.prefs_lock_none));
lockValues.remove(1); lockValues.add(LOCK_NONE);
} else if (!deviceCredentialsEnabled ||
!DeviceCredentialUtils.areCredentialsAvailable(getApplicationContext())) {
lockEntries.remove(2);
lockValues.remove(2);
} }
if (!passCodeEnabled) {
lockEntries.remove(getString(R.string.prefs_lock_using_passcode));
lockValues.remove(LOCK_PASSCODE);
} else if (!deviceCredentialsEnabled || !DeviceCredentialUtils.areCredentialsAvailable(getApplicationContext())) {
lockEntries.remove(getString(R.string.prefs_lock_using_device_credentials));
lockValues.remove(LOCK_DEVICE_CREDENTIALS);
}
String[] lockEntriesArr = new String[lockEntries.size()]; String[] lockEntriesArr = new String[lockEntries.size()];
lockEntriesArr = lockEntries.toArray(lockEntriesArr); lockEntriesArr = lockEntries.toArray(lockEntriesArr);
String[] lockValuesArr = new String[lockValues.size()]; String[] lockValuesArr = new String[lockValues.size()];
@ -706,6 +725,7 @@ public class SettingsActivity extends PreferenceActivity
lock.setEntries(lockEntriesArr); lock.setEntries(lockEntriesArr);
lock.setEntryValues(lockValuesArr); lock.setEntryValues(lockValuesArr);
lock.setSummary(lock.getEntry()); lock.setSummary(lock.getEntry());
lock.setOnPreferenceChangeListener((preference, o) -> { lock.setOnPreferenceChangeListener((preference, o) -> {
pendingLock = LOCK_NONE; pendingLock = LOCK_NONE;
String oldValue = ((ListPreference) preference).getValue(); String oldValue = ((ListPreference) preference).getValue();
@ -935,7 +955,9 @@ public class SettingsActivity extends PreferenceActivity
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ACTION_REQUEST_PASSCODE && resultCode == RESULT_OK) { if (requestCode == ACTION_REQUEST_PASSCODE && resultCode == RESULT_CANCELED) {
showPasscodeDialogIfEnforceAppProtection();
} else if (requestCode == ACTION_REQUEST_PASSCODE && resultCode == RESULT_OK) {
String passcode = data.getStringExtra(PassCodeActivity.KEY_PASSCODE); String passcode = data.getStringExtra(PassCodeActivity.KEY_PASSCODE);
if (passcode != null && passcode.length() == 4) { if (passcode != null && passcode.length() == 4) {
SharedPreferences.Editor appPrefs = PreferenceManager SharedPreferences.Editor appPrefs = PreferenceManager

View file

@ -19,5 +19,6 @@ enum class AppConfigKeys(val key: String) {
DisableClipboard("disable_clipboard"), DisableClipboard("disable_clipboard"),
DisableMoreExternalSite("disable_more_external_site"), DisableMoreExternalSite("disable_more_external_site"),
DisableIntro("disable_intro"), DisableIntro("disable_intro"),
DisableLog("disable_log") DisableLog("disable_log"),
EnforceProtection("enforce_protection")
} }

View file

@ -42,6 +42,7 @@
<bool name="disable_sharing">false</bool> <bool name="disable_sharing">false</bool>
<bool name="disable_clipboard">false</bool> <bool name="disable_clipboard">false</bool>
<bool name="disable_log">false</bool> <bool name="disable_log">false</bool>
<bool name="enforce_protection">false</bool>
<!-- Flags to enable/disable some features --> <!-- Flags to enable/disable some features -->
<string name="send_files_to_other_apps">on</string> <string name="send_files_to_other_apps">on</string>

View file

@ -35,6 +35,7 @@
<string name="app_config_base_url_title">Base URL</string> <string name="app_config_base_url_title">Base URL</string>
<string name="app_config_proxy_host_title">Proxy Hostname</string> <string name="app_config_proxy_host_title">Proxy Hostname</string>
<string name="app_config_proxy_port_title">Proxy Port</string> <string name="app_config_proxy_port_title">Proxy Port</string>
<string name="app_config_enforce_protection_title">Enforce Protection</string>
<string name="offline_operations_file_does_not_exists_yet">File does not exists, yet. Please upload the file first.</string> <string name="offline_operations_file_does_not_exists_yet">File does not exists, yet. Please upload the file first.</string>
<string name="offline_operations_worker_notification_delete_offline_folder">Delete Offline Folder</string> <string name="offline_operations_worker_notification_delete_offline_folder">Delete Offline Folder</string>
<string name="offline_operations_worker_notification_conflict_text">Conflicted Folder: %s</string> <string name="offline_operations_worker_notification_conflict_text">Conflicted Folder: %s</string>

View file

@ -61,4 +61,10 @@
android:restrictionType="bool" android:restrictionType="bool"
android:title="@string/app_config_disable_log_title" /> android:title="@string/app_config_disable_log_title" />
<restriction
android:key="enforce_protection"
android:defaultValue="false"
android:restrictionType="bool"
android:title="@string/app_config_enforce_protection_title" />
</restrictions> </restrictions>

View file

@ -35,7 +35,7 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/prefs_category_details" android:key="details"> <PreferenceCategory android:title="@string/prefs_category_details" android:key="details">
<ListPreference <com.owncloud.android.ui.ListPreferenceDialog
android:title="@string/prefs_lock" android:title="@string/prefs_lock"
android:key="lock" android:key="lock"
android:dialogTitle="@string/prefs_lock_title" android:dialogTitle="@string/prefs_lock_title"