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 =
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")
private fun <T : Any> Context.getRestriction(appConfigKey: AppConfigKeys, defaultValue: T): T {
val restrictionsManager = getSystemService(Context.RESTRICTIONS_SERVICE) as? RestrictionsManager

View file

@ -17,11 +17,13 @@ import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.utils.mdm.MDMConfig
import com.owncloud.android.R
import com.owncloud.android.authentication.AuthenticatorActivity
import com.owncloud.android.databinding.ActivitySplashBinding
import com.owncloud.android.ui.activity.BaseActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.activity.SettingsActivity
import javax.inject.Inject
class LauncherActivity : BaseActivity() {
@ -65,7 +67,11 @@ class LauncherActivity : BaseActivity() {
private fun scheduleSplashScreen() {
Handler(Looper.getMainLooper()).postDelayed({
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 {
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.ui.NextcloudWebViewClient;
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.SslUntrustedCertDialog;
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
@ -123,6 +124,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@ -1361,11 +1363,17 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
private void endSuccess() {
if (!onlyAdd) {
Intent i = new Intent(this, FileDisplayActivity.class);
i.setAction(FileDisplayActivity.RESTART);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
if (MDMConfig.INSTANCE.enforceProtection(this) && Objects.equals(preferences.getLockPreference(), SettingsActivity.LOCK_NONE)) {
Intent i = new Intent(this, SettingsActivity.class);
startActivity(i);
} else {
Intent i = new Intent(this, FileDisplayActivity.class);
i.setAction(FileDisplayActivity.RESTART);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
}
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.utils.Log_OC;
import com.owncloud.android.providers.DocumentsStorageProvider;
import com.owncloud.android.ui.ListPreferenceDialog;
import com.owncloud.android.ui.ThemeableSwitchPreference;
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
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.List;
import java.util.Objects;
import javax.inject.Inject;
@ -122,7 +124,7 @@ public class SettingsActivity extends PreferenceActivity
private Uri serverBaseUri;
private ListPreference lock;
private ListPreferenceDialog lock;
private ThemeableSwitchPreference showHiddenFiles;
private ThemeableSwitchPreference showEcosystemApps;
private AppCompatDelegate delegate;
@ -139,7 +141,6 @@ public class SettingsActivity extends PreferenceActivity
@Inject ViewThemeUtils viewThemeUtils;
@Inject ConnectivityService connectivityService;
@SuppressWarnings("deprecation")
@Override
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
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) {
@ -678,26 +688,35 @@ public class SettingsActivity extends PreferenceActivity
private void setupLockPreference(PreferenceCategory preferenceCategoryDetails,
boolean passCodeEnabled,
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)) {
ArrayList<String> lockEntries = new ArrayList<>(3);
lockEntries.add(getString(R.string.prefs_lock_none));
ArrayList<String> lockEntries = new ArrayList<>(optionSize);
lockEntries.add(getString(R.string.prefs_lock_using_passcode));
lockEntries.add(getString(R.string.prefs_lock_using_device_credentials));
ArrayList<String> lockValues = new ArrayList<>(3);
lockValues.add(LOCK_NONE);
ArrayList<String> lockValues = new ArrayList<>(optionSize);
lockValues.add(LOCK_PASSCODE);
lockValues.add(LOCK_DEVICE_CREDENTIALS);
if (!passCodeEnabled) {
lockEntries.remove(1);
lockValues.remove(1);
} else if (!deviceCredentialsEnabled ||
!DeviceCredentialUtils.areCredentialsAvailable(getApplicationContext())) {
lockEntries.remove(2);
lockValues.remove(2);
if (!enforceProtection) {
lockEntries.add(getString(R.string.prefs_lock_none));
lockValues.add(LOCK_NONE);
}
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()];
lockEntriesArr = lockEntries.toArray(lockEntriesArr);
String[] lockValuesArr = new String[lockValues.size()];
@ -706,6 +725,7 @@ public class SettingsActivity extends PreferenceActivity
lock.setEntries(lockEntriesArr);
lock.setEntryValues(lockValuesArr);
lock.setSummary(lock.getEntry());
lock.setOnPreferenceChangeListener((preference, o) -> {
pendingLock = LOCK_NONE;
String oldValue = ((ListPreference) preference).getValue();
@ -935,7 +955,9 @@ public class SettingsActivity extends PreferenceActivity
protected void onActivityResult(int requestCode, int resultCode, Intent 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);
if (passcode != null && passcode.length() == 4) {
SharedPreferences.Editor appPrefs = PreferenceManager

View file

@ -19,5 +19,6 @@ enum class AppConfigKeys(val key: String) {
DisableClipboard("disable_clipboard"),
DisableMoreExternalSite("disable_more_external_site"),
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_clipboard">false</bool>
<bool name="disable_log">false</bool>
<bool name="enforce_protection">false</bool>
<!-- Flags to enable/disable some features -->
<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_proxy_host_title">Proxy Hostname</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_worker_notification_delete_offline_folder">Delete Offline Folder</string>
<string name="offline_operations_worker_notification_conflict_text">Conflicted Folder: %s</string>

View file

@ -61,4 +61,10 @@
android:restrictionType="bool"
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>

View file

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