manager: Add selinux rules UI

This commit is contained in:
weishu
2023-06-19 22:16:46 +08:00
parent 99770a7362
commit 22d084f89b
5 changed files with 113 additions and 28 deletions

View File

@@ -8,13 +8,11 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material.icons.filled.Group
import androidx.compose.material3.AssistChip
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -25,29 +23,29 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.text.isDigitsOnly
import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.IconSource
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeker.sheets.core.utils.TestTags
import com.maxkeppeker.sheets.core.views.IconComponent
import com.maxkeppeler.sheets.input.InputDialog
import com.maxkeppeler.sheets.input.models.InputHeader
import com.maxkeppeler.sheets.input.models.InputSelection
import com.maxkeppeler.sheets.input.models.InputTextField
import com.maxkeppeler.sheets.input.models.InputTextFieldType
import com.maxkeppeler.sheets.input.models.ValidationResult
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
@@ -55,8 +53,9 @@ import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.profile.Capabilities
import me.weishu.kernelsu.profile.Groups
import me.weishu.kernelsu.ui.util.isSepolicyValid
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RootProfileConfig(
modifier: Modifier = Modifier,
@@ -172,26 +171,15 @@ fun RootProfileConfig(
)
}
ListItem(headlineContent = {
val keyboardController = LocalSoftwareKeyboardController.current
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
label = { Text(text = stringResource(R.string.profile_selinux_context)) },
value = profile.context,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Ascii,
imeAction = ImeAction.Done,
),
keyboardActions = KeyboardActions(onDone = {
keyboardController?.hide()
}),
onValueChange = {
onProfileChange(profile.copy(context = it, rootUseDefault = false))
}
SELinuxPanel(profile = profile, onSELinuxChange = { domain, rules ->
onProfileChange(
profile.copy(
context = domain,
rootUseDefault = false
)
)
})
}
}
@@ -368,6 +356,89 @@ private fun UidPanel(uid: Int, label: String, onUidChange: (Int) -> Unit) {
})
}
@Composable
private fun SELinuxPanel(profile: Natives.Profile, onSELinuxChange: (domain: String, rules: String) -> Unit) {
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
var domain by remember { mutableStateOf(profile.context) }
var rules by remember { mutableStateOf("") }
val inputOptions = listOf(
InputTextField(
text = domain,
header = InputHeader(
title = stringResource(id = R.string.profile_selinux_domain),
),
type = InputTextFieldType.OUTLINED,
required = true,
resultListener = {
domain = it ?: ""
},
validationListener = { value ->
// value can be a-zA-Z0-9_
val regex = Regex("^[a-z_]+:[a-z0-9_]+:[a-z0-9_]+(:[a-z0-9_]+)?$")
if (value?.matches(regex) == true) ValidationResult.Valid
else ValidationResult.Invalid("Domain must be valid sepolicy")
}
),
InputTextField(
text = rules,
header = InputHeader(
title = stringResource(id = R.string.profile_selinux_rules),
),
type = InputTextFieldType.OUTLINED,
singleLine = false,
resultListener = {
rules = it ?: ""
},
validationListener = { value ->
if (isSepolicyValid(value)) ValidationResult.Valid
else ValidationResult.Invalid("Rules must be valid sepolicy")
}
)
)
InputDialog(
state = rememberUseCaseState(visible = true,
onFinishedRequest = {
onSELinuxChange(domain, rules)
},
onCloseRequest = {
showDialog = false
}),
header = Header.Default(
title = stringResource(R.string.profile_selinux_context),
),
selection = InputSelection(
input = inputOptions,
onPositiveClick = { result ->
// Handle selection
},
)
)
}
ListItem(headlineContent = {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.clickable {
showDialog = true
},
enabled = false,
colors = TextFieldDefaults.outlinedTextFieldColors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledBorderColor = MaterialTheme.colorScheme.outline,
disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant
),
label = { Text(text = stringResource(R.string.profile_selinux_context)) },
value = profile.context,
onValueChange = { },
)
})
}
@Preview
@Composable
private fun RootProfileConfigPreview() {

View File

@@ -140,4 +140,14 @@ fun hasMagisk(): Boolean {
val result = shell.newJob().add("nsenter --mount=/proc/1/ns/mnt which magisk").exec()
Log.i(TAG, "has magisk: ${result.isSuccess}")
return result.isSuccess
}
fun isSepolicyValid(rules: String?): Boolean {
if (rules == null) {
return true
}
val shell = getRootShell()
val result =
shell.newJob().add("ksud sepolicy check '$rules'").to(ArrayList(), null).exec()
return result.isSuccess
}

View File

@@ -74,4 +74,6 @@
<string name="settings_umount_modules_default">Umount modules by default</string>
<string name="settings_umount_modules_default_summary">The global default value for \"Umount modules\" in App Profiles. If enabled, it will remove all module modifications to the system for applications that do not have a Profile set.</string>
<string name="profile_umount_modules_summary">Enabling this option will allow KernelSU to restore any modified files by the modules for this application.</string>
<string name="profile_selinux_domain">Domain</string>
<string name="profile_selinux_rules">Rules</string>
</resources>