Skip to content

Commit

Permalink
Checking if device is secure.
Browse files Browse the repository at this point in the history
Adding device to security checks.
Modifying and adding tests for new security checks.
  • Loading branch information
manuelplazaspalacio committed Jul 5, 2023
1 parent 438f658 commit 7f73c8d
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 29 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Summary
* Enhancement - Added "Open in web" options to main file list: [#3860](https://github.com/owncloud/android/issues/3860)
* Enhancement - Copy/move conflict solved by users: [#3935](https://github.com/owncloud/android/issues/3935)
* Enhancement - File name conflict starting by (1): [#4040](https://github.com/owncloud/android/pull/4040)
* Enhancement - Force security if not protected: [#4061](https://github.com/owncloud/android/issues/4061)

Details
-------
Expand Down Expand Up @@ -73,6 +74,13 @@ Details
https://github.com/owncloud/android/issues/3946
https://github.com/owncloud/android/pull/4040

* Enhancement - Force security if not protected: [#4061](https://github.com/owncloud/android/issues/4061)

Checking phone protection and branding boolean device_protection to enforce the security.

https://github.com/owncloud/android/issues/4061
https://github.com/owncloud/android/pull/4087

Changelog for ownCloud Android Client [4.0.0] (2023-05-29)
=======================================
The following sections list the changes in ownCloud Android Client 4.0.0 relevant to
Expand Down
6 changes: 6 additions & 0 deletions changelog/unreleased/4087
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: Force security if not protected

A new branding parameter was created to enforce security protection in the app if device protection is not enabled.

https://github.com/owncloud/android/issues/4061
https://github.com/owncloud/android/pull/4087
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import com.owncloud.android.presentation.security.SecurityEnforced
import com.owncloud.android.presentation.security.biometric.BiometricActivity
import com.owncloud.android.presentation.security.biometric.BiometricStatus
import com.owncloud.android.presentation.security.biometric.EnableBiometrics
import com.owncloud.android.presentation.security.isDeviceSecure
import com.owncloud.android.presentation.security.passcode.PassCodeActivity
import com.owncloud.android.presentation.security.pattern.PatternActivity
import com.owncloud.android.presentation.settings.privacypolicy.PrivacyPolicyActivity
Expand Down Expand Up @@ -285,39 +286,44 @@ fun Activity.hideSoftKeyboard() {
fun Activity.checkPasscodeEnforced(securityEnforced: SecurityEnforced) {
val sharedPreferencesProvider = OCSharedPreferencesProvider(this)

val deviceProtection: Boolean = !this.resources.getBoolean(R.bool.device_protection) && !isDeviceSecure()
val lockEnforced: Int = this.resources.getInteger(R.integer.lock_enforced)
val passcodeConfigured = sharedPreferencesProvider.getBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, false)
val patternConfigured = sharedPreferencesProvider.getBoolean(PatternActivity.PREFERENCE_SET_PATTERN, false)

when (parseFromInteger(lockEnforced)) {
LockEnforcedType.DISABLED -> {}
LockEnforcedType.EITHER_ENFORCED -> {
if (!passcodeConfigured && !patternConfigured) {
val options = arrayOf(getString(R.string.security_enforced_first_option), getString(R.string.security_enforced_second_option))
var optionSelected = 0

AlertDialog.Builder(this)
.setCancelable(false)
.setTitle(getString(R.string.security_enforced_title))
.setSingleChoiceItems(options, LockType.PASSCODE.ordinal) { _, which -> optionSelected = which }
.setPositiveButton(android.R.string.ok) { dialog, _ ->
when (LockType.parseFromInteger(optionSelected)) {
LockType.PASSCODE -> securityEnforced.optionLockSelected(LockType.PASSCODE)
LockType.PATTERN -> securityEnforced.optionLockSelected(LockType.PATTERN)
if (deviceProtection) {
when (parseFromInteger(lockEnforced)) {
LockEnforcedType.DISABLED -> {}
LockEnforcedType.EITHER_ENFORCED -> {
if (!passcodeConfigured && !patternConfigured) {
val options = arrayOf(getString(R.string.security_enforced_first_option), getString(R.string.security_enforced_second_option))
var optionSelected = 0

AlertDialog.Builder(this)
.setCancelable(false)
.setTitle(getString(R.string.security_enforced_title))
.setSingleChoiceItems(options, LockType.PASSCODE.ordinal) { _, which -> optionSelected = which }
.setPositiveButton(android.R.string.ok) { dialog, _ ->
when (LockType.parseFromInteger(optionSelected)) {
LockType.PASSCODE -> securityEnforced.optionLockSelected(LockType.PASSCODE)
LockType.PATTERN -> securityEnforced.optionLockSelected(LockType.PATTERN)
}
dialog.dismiss()
}
dialog.dismiss()
}
.show()
.show()
}
}
}
LockEnforcedType.PASSCODE_ENFORCED -> {
if (!passcodeConfigured) {
manageOptionLockSelected(LockType.PASSCODE)

LockEnforcedType.PASSCODE_ENFORCED -> {
if (!passcodeConfigured) {
manageOptionLockSelected(LockType.PASSCODE)
}
}
}
LockEnforcedType.PATTERN_ENFORCED -> {
if (!patternConfigured) {
manageOptionLockSelected(LockType.PATTERN)

LockEnforcedType.PATTERN_ENFORCED -> {
if (!patternConfigured) {
manageOptionLockSelected(LockType.PATTERN)
}
}
}
}
Expand All @@ -344,6 +350,7 @@ fun Activity.manageOptionLockSelected(type: LockType) {
flags = FLAG_ACTIVITY_NO_HISTORY
putExtra(EXTRAS_LOCK_ENFORCED, true)
})

LockType.PATTERN -> startActivity(Intent(this, PatternActivity::class.java).apply {
action = PatternActivity.ACTION_REQUEST_WITH_RESULT
flags = FLAG_ACTIVITY_NO_HISTORY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

package com.owncloud.android.presentation.security

import android.app.KeyguardManager
import android.content.Context
import android.os.SystemClock
import com.owncloud.android.MainApp
import com.owncloud.android.data.preferences.datasources.implementation.OCSharedPreferencesProvider
Expand Down Expand Up @@ -68,3 +70,5 @@ fun bayPassUnlockOnce() {
preferencesProvider.putLong(PREFERENCE_LAST_UNLOCK_TIMESTAMP, newLastUnlockTimestamp)
}
}

fun isDeviceSecure() = (MainApp.appContext.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager).isDeviceSecure
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ import com.owncloud.android.presentation.security.LockEnforcedType
import com.owncloud.android.presentation.security.LockEnforcedType.Companion.parseFromInteger
import com.owncloud.android.presentation.security.LockTimeout
import com.owncloud.android.presentation.security.biometric.BiometricActivity
import com.owncloud.android.presentation.security.isDeviceSecure
import com.owncloud.android.presentation.security.passcode.PassCodeActivity
import com.owncloud.android.presentation.security.pattern.PatternActivity
import com.owncloud.android.providers.MdmProvider
import com.owncloud.android.utils.CONFIGURATION_DEVICE_PROTECTION
import com.owncloud.android.utils.CONFIGURATION_LOCK_DELAY_TIME
import com.owncloud.android.utils.NO_MDM_RESTRICTION_YET

Expand All @@ -51,7 +53,8 @@ class SettingsSecurityViewModel(
fun getBiometricsState(): Boolean = preferencesProvider.getBoolean(BiometricActivity.PREFERENCE_SET_BIOMETRIC, false)

fun isSecurityEnforcedEnabled() =
parseFromInteger(mdmProvider.getBrandingInteger(NO_MDM_RESTRICTION_YET, R.integer.lock_enforced)) != LockEnforcedType.DISABLED
(!mdmProvider.getBrandingBoolean(CONFIGURATION_DEVICE_PROTECTION, R.bool.device_protection) || !isDeviceSecure()) &&
parseFromInteger(mdmProvider.getBrandingInteger(NO_MDM_RESTRICTION_YET, R.integer.lock_enforced)) != LockEnforcedType.DISABLED

fun isLockDelayEnforcedEnabled() = LockTimeout.parseFromInteger(
mdmProvider.getBrandingInteger(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.owncloud.android.presentation.security.LockTimeout
import com.owncloud.android.presentation.security.PREFERENCE_LOCK_TIMEOUT
import com.owncloud.android.providers.MdmProvider
import com.owncloud.android.utils.CONFIGURATION_ALLOW_SCREENSHOTS
import com.owncloud.android.utils.CONFIGURATION_DEVICE_PROTECTION
import com.owncloud.android.utils.CONFIGURATION_LOCK_DELAY_TIME
import com.owncloud.android.utils.CONFIGURATION_OAUTH2_OPEN_ID_PROMPT
import com.owncloud.android.utils.CONFIGURATION_OAUTH2_OPEN_ID_SCOPE
Expand All @@ -52,6 +53,7 @@ class SplashActivity : AppCompatActivity() {
cacheBooleanRestriction(CONFIGURATION_ALLOW_SCREENSHOTS, R.string.allow_screenshots_configuration_feedback_ok)
cacheStringRestriction(CONFIGURATION_OAUTH2_OPEN_ID_SCOPE, R.string.oauth2_open_id_scope_configuration_feedback_ok)
cacheStringRestriction(CONFIGURATION_OAUTH2_OPEN_ID_PROMPT, R.string.oauth2_open_id_prompt_configuration_feedback_ok)
cacheBooleanRestriction(CONFIGURATION_DEVICE_PROTECTION, R.string.device_protection_configuration_feedback_ok)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const val CONFIGURATION_SERVER_URL_INPUT_VISIBILITY = "server_url_input_visibili
const val CONFIGURATION_ALLOW_SCREENSHOTS = "allow_screenshots_configuration"
const val CONFIGURATION_OAUTH2_OPEN_ID_SCOPE = "oauth2_open_id_scope"
const val CONFIGURATION_OAUTH2_OPEN_ID_PROMPT = "oauth2_open_id_prompt"
const val CONFIGURATION_DEVICE_PROTECTION = "device_protection"

@StringDef(
NO_MDM_RESTRICTION_YET,
Expand All @@ -40,6 +41,7 @@ const val CONFIGURATION_OAUTH2_OPEN_ID_PROMPT = "oauth2_open_id_prompt"
CONFIGURATION_ALLOW_SCREENSHOTS,
CONFIGURATION_OAUTH2_OPEN_ID_SCOPE,
CONFIGURATION_OAUTH2_OPEN_ID_PROMPT,
CONFIGURATION_DEVICE_PROTECTION,
)
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER)
Expand Down
1 change: 1 addition & 0 deletions owncloudApp/src/main/res/values/setup.xml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
<integer name="passcode_digits">4</integer>

<!--Security enforced -->
<bool name="device_protection">false</bool>
<integer name="lock_enforced">0</integer>
<integer name="lock_delay_enforced">0</integer>

Expand Down
3 changes: 3 additions & 0 deletions owncloudApp/src/main/res/values/strings__not_to_translate.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<string name="allow_screenshots_configuration_description">Enables or disables the possibility to allow screenshots or not</string>
<string name="oauth2_open_id_scope_configuration_title">OpenID Connect scope</string>
<string name="oauth2_open_id_scope_configuration_description">Indicates the scope for OpenID Connect</string>
<string name="device_protection_configuration_title">Device Protection</string>
<string name="device_protection_configuration_description">Indicates if its forced the device protection</string>

<!-- MDM feedback -->
<string name="lock_delay_configuration_feedback_ok">The lock delay was set correctly</string>
Expand All @@ -37,6 +39,7 @@
<string name="allow_screenshots_configuration_feedback_ok">The preference for allowing screenshots was set correctly</string>
<string name="oauth2_open_id_scope_configuration_feedback_ok">The OpenID Connect scope was set correctly</string>
<string name="oauth2_open_id_prompt_configuration_feedback_ok">The OpenID Connect prompt was set correctly</string>
<string name="device_protection_configuration_feedback_ok">The device protection was set correctly</string>

<!-- Spaces -->
<string name="bottom_nav_spaces">Spaces</string>
Expand Down
6 changes: 6 additions & 0 deletions owncloudApp/src/main/res/xml/managed_configurations.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@
android:title="@string/oauth2_open_id_scope_configuration_title"
android:description="@string/oauth2_open_id_scope_configuration_description"
android:defaultValue="" />
<restriction
android:key="device_protection"
android:restrictionType="bool"
android:title="@string/oauth2_open_id_scope_configuration_title"
android:description="@string/oauth2_open_id_scope_configuration_description"
android:defaultValue="false" />
</restrictions>
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ package com.owncloud.android.presentation.viewmodels.settings
import com.owncloud.android.R
import com.owncloud.android.data.preferences.datasources.SharedPreferencesProvider
import com.owncloud.android.presentation.security.LockEnforcedType
import com.owncloud.android.presentation.security.isDeviceSecure
import com.owncloud.android.presentation.security.passcode.PassCodeActivity
import com.owncloud.android.presentation.security.pattern.PatternActivity
import com.owncloud.android.presentation.settings.security.SettingsSecurityViewModel
Expand All @@ -31,6 +32,7 @@ import com.owncloud.android.presentation.viewmodels.ViewModelTest
import com.owncloud.android.providers.MdmProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Assert.assertFalse
Expand All @@ -49,6 +51,7 @@ class SettingsSecurityViewModelTest : ViewModelTest() {
preferencesProvider = mockk(relaxUnitFun = true)
mdmProvider = mockk(relaxUnitFun = true)
securityViewModel = SettingsSecurityViewModel(preferencesProvider, mdmProvider)
mockkStatic(::isDeviceSecure)
}

@Test
Expand Down Expand Up @@ -140,22 +143,86 @@ class SettingsSecurityViewModelTest : ViewModelTest() {
}

@Test
fun `is security enforced enabled - ok - true`() {
fun `is security enforced enabled device protection device secure - ok - false`() {
every { isDeviceSecure() } returns true
every { mdmProvider.getBrandingBoolean(any(), R.bool.device_protection) } returns true
every { mdmProvider.getBrandingInteger(any(), R.integer.lock_enforced) } returns LockEnforcedType.EITHER_ENFORCED.ordinal

val result = securityViewModel.isSecurityEnforcedEnabled()
assertFalse(result)

}

@Test
fun `is security enforced disabled device protection device no secure - ok - false`() {
every { isDeviceSecure() } returns false
every { mdmProvider.getBrandingBoolean(any(), R.bool.device_protection) } returns true
every { mdmProvider.getBrandingInteger(any(), R.integer.lock_enforced) } returns LockEnforcedType.DISABLED.ordinal

val result = securityViewModel.isSecurityEnforcedEnabled()
assertFalse(result)
}

@Test
fun `is security enforced enabled no device protection device no secure - ok - true`() {
every { isDeviceSecure() } returns false
every { mdmProvider.getBrandingBoolean(any(), R.bool.device_protection) } returns false
every { mdmProvider.getBrandingInteger(any(), R.integer.lock_enforced) } returns LockEnforcedType.EITHER_ENFORCED.ordinal

val result = securityViewModel.isSecurityEnforcedEnabled()
assertTrue(result)
}

@Test
fun `is security enforced enabled no device protection device secure - ok - true`() {
every { isDeviceSecure() } returns true
every { mdmProvider.getBrandingBoolean(any(), R.bool.device_protection) } returns false
every { mdmProvider.getBrandingInteger(any(), R.integer.lock_enforced) } returns LockEnforcedType.EITHER_ENFORCED.ordinal

val result = securityViewModel.isSecurityEnforcedEnabled()
assertTrue(result)
}

@Test
fun `is security enforced disabled no device protection device no secure - ok - false`() {
every { isDeviceSecure() } returns false
every { mdmProvider.getBrandingBoolean(any(), R.bool.device_protection) } returns false
every { mdmProvider.getBrandingInteger(any(), R.integer.lock_enforced) } returns LockEnforcedType.DISABLED.ordinal

val result = securityViewModel.isSecurityEnforcedEnabled()
assertFalse(result)
}

@Test
fun `is security enforced enabled - ok - false`() {
fun `is security enforced disabled no device protection device secure - ok - false`() {
every { isDeviceSecure() } returns true
every { mdmProvider.getBrandingBoolean(any(), R.bool.device_protection) } returns false
every { mdmProvider.getBrandingInteger(any(), R.integer.lock_enforced) } returns LockEnforcedType.DISABLED.ordinal

val result = securityViewModel.isSecurityEnforcedEnabled()
assertFalse(result)
}

@Test
fun `is security enforced disabled device protection device secure - ok - false`() {
every { isDeviceSecure() } returns true
every { mdmProvider.getBrandingBoolean(any(), R.bool.device_protection) } returns true
every { mdmProvider.getBrandingInteger(any(), R.integer.lock_enforced) } returns LockEnforcedType.DISABLED.ordinal

val result = securityViewModel.isSecurityEnforcedEnabled()
assertFalse(result)
}

@Test
fun `is security enforced enabled device protection device no secure - ok - true`() {
every { isDeviceSecure() } returns false
every { mdmProvider.getBrandingBoolean(any(), R.bool.device_protection) } returns true
every { mdmProvider.getBrandingInteger(any(), R.integer.lock_enforced) } returns LockEnforcedType.EITHER_ENFORCED.ordinal

val result = securityViewModel.isSecurityEnforcedEnabled()
assertTrue(result)
}

@Test
fun `is lock delay enforced enabled - ok - true`() {
every { mdmProvider.getBrandingInteger(any(), R.integer.lock_delay_enforced) } returns 1
Expand Down

0 comments on commit 7f73c8d

Please sign in to comment.