check super permission: win && linux

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages 2022-08-19 15:44:19 +08:00
parent 123a7aa17b
commit 4faf0a3d35
7 changed files with 208 additions and 78 deletions

View File

@ -52,6 +52,8 @@ class MyTheme {
static const Color darkGray = Color(0xFFB9BABC); static const Color darkGray = Color(0xFFB9BABC);
static const Color cmIdColor = Color(0xFF21790B); static const Color cmIdColor = Color(0xFF21790B);
static const Color dark = Colors.black87; static const Color dark = Colors.black87;
static const Color disabledTextLight = Color(0xFF888888);
static const Color disabledTextDark = Color(0xFF777777);
static ThemeData lightTheme = ThemeData( static ThemeData lightTheme = ThemeData(
brightness: Brightness.light, brightness: Brightness.light,

View File

@ -253,28 +253,47 @@ class _Safety extends StatefulWidget {
class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
bool locked = true;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return ListView( return ListView(
children: [ children: [
permissions(), Column(
password(), children: [
whitelist(), _lock(locked, 'Unlock Security Settings', () {
locked = false;
setState(() => {});
}),
AbsorbPointer(
absorbing: locked,
child: Column(children: [
permissions(),
password(),
whitelist(),
]),
),
],
)
], ],
).marginOnly(bottom: _kListViewBottomMargin); ).marginOnly(bottom: _kListViewBottomMargin);
} }
Widget permissions() { Widget permissions() {
bool enabled = !locked;
return _Card(title: 'Permissions', children: [ return _Card(title: 'Permissions', children: [
_OptionCheckBox('Enable Keyboard/Mouse', 'enable-keyboard'), _OptionCheckBox('Enable Keyboard/Mouse', 'enable-keyboard',
_OptionCheckBox('Enable Clipboard', 'enable-clipboard'), enabled: enabled),
_OptionCheckBox('Enable File Transfer', 'enable-file-transfer'), _OptionCheckBox('Enable Clipboard', 'enable-clipboard', enabled: enabled),
_OptionCheckBox('Enable Audio', 'enable-audio'), _OptionCheckBox('Enable File Transfer', 'enable-file-transfer',
_OptionCheckBox('Enable Remote Restart', 'enable-remote-restart'), enabled: enabled),
_OptionCheckBox('Enable Audio', 'enable-audio', enabled: enabled),
_OptionCheckBox('Enable Remote Restart', 'enable-remote-restart',
enabled: enabled),
_OptionCheckBox('Enable remote configuration modification', _OptionCheckBox('Enable remote configuration modification',
'allow-remote-config-modification'), 'allow-remote-config-modification',
enabled: enabled),
]); ]);
} }
@ -297,15 +316,17 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
String currentValue = values[keys.indexOf(model.verificationMethod)]; String currentValue = values[keys.indexOf(model.verificationMethod)];
List<Widget> radios = values List<Widget> radios = values
.map((value) => _Radio<String>( .map((value) => _Radio<String>(
value: value, value: value,
groupValue: currentValue, groupValue: currentValue,
label: value, label: value,
onChanged: ((value) { onChanged: ((value) {
model.verificationMethod = keys[values.indexOf(value)]; model.verificationMethod = keys[values.indexOf(value)];
}))) }),
enabled: !locked,
))
.toList(); .toList();
var onChanged = tmp_enabled var onChanged = tmp_enabled && !locked
? (value) { ? (value) {
if (value != null) if (value != null)
model.temporaryPasswordLength = value.toString(); model.temporaryPasswordLength = value.toString();
@ -319,7 +340,11 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
value: value, value: value,
groupValue: model.temporaryPasswordLength, groupValue: model.temporaryPasswordLength,
onChanged: onChanged), onChanged: onChanged),
Text(value), Text(
value,
style: TextStyle(
color: _disabledTextColor(onChanged != null)),
),
], ],
).paddingSymmetric(horizontal: 10), ).paddingSymmetric(horizontal: 10),
onTap: () => onChanged?.call(value), onTap: () => onChanged?.call(value),
@ -335,10 +360,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
...lengthRadios, ...lengthRadios,
], ],
), ),
enabled: tmp_enabled), enabled: tmp_enabled && !locked),
radios[1], radios[1],
_SubButton( _SubButton('Set permanent password', setPasswordDialog,
'Set permanent password', setPasswordDialog, perm_enabled), perm_enabled && !locked),
radios[2], radios[2],
]); ]);
}))); })));
@ -346,7 +371,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
Widget whitelist() { Widget whitelist() {
return _Card(title: 'IP Whitelisting', children: [ return _Card(title: 'IP Whitelisting', children: [
_Button('IP Whitelisting', changeWhiteList, tip: 'whitelist_tip') _Button('IP Whitelisting', changeWhiteList,
tip: 'whitelist_tip', enabled: !locked)
]); ]);
} }
} }
@ -362,31 +388,46 @@ class _ConnectionState extends State<_Connection>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
bool locked = true;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return ListView( bool enabled = !locked;
children: [ return ListView(children: [
_Card(title: 'Server', children: [ Column(
_Button('ID/Relay Server', changeServer), children: [
]), _lock(locked, 'Unlock Connection Settings', () {
_Card(title: 'Service', children: [ locked = false;
_OptionCheckBox('Enable Service', 'stop-service', reverse: true), setState(() => {});
// TODO: Not implemented }),
// _option_check('Always connected via relay', 'allow-always-relay'), AbsorbPointer(
// _option_check('Start ID/relay service', 'stop-rendezvous-service', absorbing: locked,
// reverse: true), child: Column(children: [
]), _Card(title: 'Server', children: [
_Card(title: 'TCP Tunneling', children: [ _Button('ID/Relay Server', changeServer, enabled: enabled),
_OptionCheckBox('Enable TCP Tunneling', 'enable-tunnel'), ]),
]), _Card(title: 'Service', children: [
direct_ip(), _OptionCheckBox('Enable Service', 'stop-service',
_Card(title: 'Proxy', children: [ reverse: true, enabled: enabled),
_Button('Socks5 Proxy', changeSocks5Proxy), // TODO: Not implemented
]), // _option_check('Always connected via relay', 'allow-always-relay', enabled: enabled),
], // _option_check('Start ID/relay service', 'stop-rendezvous-service',
).marginOnly(bottom: _kListViewBottomMargin); // reverse: true, enabled: enabled),
]),
_Card(title: 'TCP Tunneling', children: [
_OptionCheckBox('Enable TCP Tunneling', 'enable-tunnel',
enabled: enabled),
]),
direct_ip(),
_Card(title: 'Proxy', children: [
_Button('Socks5 Proxy', changeSocks5Proxy, enabled: enabled),
]),
]),
),
],
)
]).marginOnly(bottom: _kListViewBottomMargin);
} }
Widget direct_ip() { Widget direct_ip() {
@ -395,7 +436,7 @@ class _ConnectionState extends State<_Connection>
RxBool apply_enabled = false.obs; RxBool apply_enabled = false.obs;
return _Card(title: 'Direct IP Access', children: [ return _Card(title: 'Direct IP Access', children: [
_OptionCheckBox('Enable Direct IP Access', 'direct-server', _OptionCheckBox('Enable Direct IP Access', 'direct-server',
update: update), update: update, enabled: !locked),
_futureBuilder( _futureBuilder(
future: () async { future: () async {
String enabled = await bind.mainGetOption(key: 'direct-server'); String enabled = await bind.mainGetOption(key: 'direct-server');
@ -414,7 +455,7 @@ class _ConnectionState extends State<_Connection>
width: 80, width: 80,
child: TextField( child: TextField(
controller: controller, controller: controller,
enabled: enabled, enabled: enabled && !locked,
onChanged: (_) => apply_enabled.value = true, onChanged: (_) => apply_enabled.value = true,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.allow(RegExp( FilteringTextInputFormatter.allow(RegExp(
@ -429,10 +470,10 @@ class _ConnectionState extends State<_Connection>
), ),
), ),
), ),
enabled: enabled, enabled: enabled && !locked,
), ).marginOnly(left: 5),
Obx(() => ElevatedButton( Obx(() => ElevatedButton(
onPressed: apply_enabled.value && enabled onPressed: apply_enabled.value && enabled && !locked
? () async { ? () async {
apply_enabled.value = false; apply_enabled.value = false;
await bind.mainSetOption( await bind.mainSetOption(
@ -440,7 +481,9 @@ class _ConnectionState extends State<_Connection>
value: controller.text); value: controller.text);
} }
: null, : null,
child: Text(translate('Apply')), child: Text(
translate('Apply'),
),
).marginOnly(left: 20)) ).marginOnly(left: 20))
]); ]);
}, },
@ -700,8 +743,16 @@ Widget _Card({required String title, required List<Widget> children}) {
); );
} }
Color? _disabledTextColor(bool enabled) {
return enabled
? null
: isDarkTheme()
? MyTheme.disabledTextDark
: MyTheme.disabledTextLight;
}
Widget _OptionCheckBox(String label, String key, Widget _OptionCheckBox(String label, String key,
{Function()? update = null, bool reverse = false}) { {Function()? update = null, bool reverse = false, bool enabled = true}) {
return _futureBuilder( return _futureBuilder(
future: bind.mainGetOption(key: key), future: bind.mainGetOption(key: key),
hasData: (data) { hasData: (data) {
@ -721,9 +772,14 @@ Widget _OptionCheckBox(String label, String key,
child: Obx( child: Obx(
() => Row( () => Row(
children: [ children: [
Checkbox(value: ref.value, onChanged: onChanged) Checkbox(
value: ref.value, onChanged: enabled ? onChanged : null)
.marginOnly(right: 10), .marginOnly(right: 10),
Expanded(child: Text(translate(label))) Expanded(
child: Text(
translate(label),
style: TextStyle(color: _disabledTextColor(enabled)),
))
], ],
), ),
).marginOnly(left: _kCheckBoxLeftMargin), ).marginOnly(left: _kCheckBoxLeftMargin),
@ -734,29 +790,33 @@ Widget _OptionCheckBox(String label, String key,
}); });
} }
Widget _Radio<T>({ Widget _Radio<T>(
required T value, {required T value,
required T groupValue, required T groupValue,
required String label, required String label,
required Function(T value) onChanged, required Function(T value) onChanged,
}) { bool enabled = true}) {
var on_change = (T? value) { var on_change = enabled
if (value != null) { ? (T? value) {
onChanged(value); if (value != null) {
} onChanged(value);
}; }
}
: null;
return GestureDetector( return GestureDetector(
child: Row( child: Row(
children: [ children: [
Radio<T>(value: value, groupValue: groupValue, onChanged: on_change), Radio<T>(value: value, groupValue: groupValue, onChanged: on_change),
Expanded( Expanded(
child: Text(translate(label), child: Text(translate(label),
style: TextStyle(fontSize: _kContentFontSize)) style: TextStyle(
fontSize: _kContentFontSize,
color: _disabledTextColor(enabled)))
.marginOnly(left: 5), .marginOnly(left: 5),
), ),
], ],
).marginOnly(left: _kRadioLeftMargin), ).marginOnly(left: _kRadioLeftMargin),
onTap: () => on_change(value), onTap: () => on_change?.call(value),
); );
} }
@ -808,19 +868,19 @@ Widget _SubLabeledWidget(String label, Widget child, {bool enabled = true}) {
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: hover.value && enabled color: hover.value && enabled
? Colors.grey.withOpacity(0.8) ? Color(0xFFD7D7D7)
: Colors.grey.withOpacity(0.5), : Color(0xFFCBCBCB),
width: hover.value && enabled ? 2 : 1)), width: hover.value && enabled ? 2 : 1)),
child: Row( child: Row(
children: [ children: [
Container( Container(
height: 28, height: 28,
color: (hover.value && enabled) color: (hover.value && enabled)
? Colors.grey.withOpacity(0.8) ? Color(0xFFD7D7D7)
: Colors.grey.withOpacity(0.5), : Color(0xFFCBCBCB),
child: Text( child: Text(
label + ': ', label + ': ',
style: TextStyle(), style: TextStyle(fontWeight: FontWeight.w300),
), ),
alignment: Alignment.center, alignment: Alignment.center,
padding: padding:
@ -851,6 +911,43 @@ Widget _futureBuilder(
}); });
} }
Widget _lock(
bool locked,
String label,
Function() onUnlock,
) {
return Offstage(
offstage: !locked,
child: Row(
children: [
Container(
width: _kCardFixedWidth,
child: Card(
child: ElevatedButton(
child: Container(
height: 25,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.security_sharp,
size: 20,
),
Text(translate(label)).marginOnly(left: 5),
]).marginSymmetric(vertical: 2)),
onPressed: () async {
bool checked = await bind.mainCheckSuperUserPermission();
if (checked) {
onUnlock();
}
},
).marginSymmetric(horizontal: 2, vertical: 4),
).marginOnly(left: _kCardLeftMargin),
).marginOnly(top: 10),
],
));
}
// ignore: must_be_immutable // ignore: must_be_immutable
class _ComboBox extends StatelessWidget { class _ComboBox extends StatelessWidget {
late final List<String> keys; late final List<String> keys;

View File

@ -70,7 +70,6 @@ class DesktopTabBar extends StatelessWidget {
super(key: key) { super(key: key) {
scrollController.itemCount = tabs.length; scrollController.itemCount = tabs.length;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
debugPrint("callback");
scrollController.scrollToItem(selected.value, scrollController.scrollToItem(selected.value,
center: true, animate: true); center: true, animate: true);
}); });

View File

@ -22,12 +22,12 @@ use crate::ui_interface;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_interface::{change_id, check_connect_status, is_ok_change_id}; use crate::ui_interface::{change_id, check_connect_status, is_ok_change_id};
use crate::ui_interface::{ use crate::ui_interface::{
discover, forget_password, get_api_server, get_app_name, get_async_job_status, check_super_user_permission, discover, forget_password, get_api_server, get_app_name,
get_connect_status, get_fav, get_id, get_lan_peers, get_langs, get_license, get_local_option, get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers, get_langs,
get_option, get_options, get_peer, get_peer_option, get_socks, get_sound_inputs, get_uuid, get_license, get_local_option, get_option, get_options, get_peer, get_peer_option, get_socks,
get_version, has_hwcodec, has_rendezvous_service, post_request, set_local_option, set_option, get_sound_inputs, get_uuid, get_version, has_hwcodec, has_rendezvous_service, post_request,
set_options, set_peer_option, set_permanent_password, set_socks, store_fav, set_local_option, set_option, set_options, set_peer_option, set_permanent_password, set_socks,
test_if_valid_server, update_temporary_password, using_public_server, store_fav, test_if_valid_server, update_temporary_password, using_public_server,
}; };
fn initialize(app_dir: &str) { fn initialize(app_dir: &str) {
@ -735,6 +735,10 @@ pub fn main_set_permanent_password(password: String) {
set_permanent_password(password); set_permanent_password(password);
} }
pub fn main_check_super_user_permission() -> bool {
check_super_user_permission()
}
pub fn cm_send_chat(conn_id: i32, msg: String) { pub fn cm_send_chat(conn_id: i32, msg: String) {
connection_manager::send_chat(conn_id, msg); connection_manager::send_chat(conn_id, msg);
} }

View File

@ -629,3 +629,9 @@ extern "C" {
pub fn quit_gui() { pub fn quit_gui() {
unsafe { gtk_main_quit() }; unsafe { gtk_main_quit() };
} }
pub fn check_super_user_permission() -> ResultType<bool> {
// TODO: replace echo with a rustdesk's program, which is location-fixed and non-gui.
let status = std::process::Command::new("pkexec").arg("echo").status()?;
Ok(status.success() && status.code() == Some(0))
}

View File

@ -8,7 +8,7 @@ use hbb_common::{
}; };
use std::io::prelude::*; use std::io::prelude::*;
use std::{ use std::{
ffi::OsString, ffi::{CString, OsString},
fs, io, mem, fs, io, mem,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
time::{Duration, Instant}, time::{Duration, Instant},
@ -17,7 +17,8 @@ use winapi::{
shared::{minwindef::*, ntdef::NULL, windef::*}, shared::{minwindef::*, ntdef::NULL, windef::*},
um::{ um::{
errhandlingapi::GetLastError, handleapi::CloseHandle, minwinbase::STILL_ACTIVE, errhandlingapi::GetLastError, handleapi::CloseHandle, minwinbase::STILL_ACTIVE,
processthreadsapi::GetExitCodeProcess, winbase::*, wingdi::*, winnt::HANDLE, winuser::*, processthreadsapi::GetExitCodeProcess, shellapi::ShellExecuteA, winbase::*, wingdi::*,
winnt::HANDLE, winuser::*,
}, },
}; };
use windows_service::{ use windows_service::{
@ -1418,3 +1419,17 @@ pub fn get_user_token(session_id: u32, as_user: bool) -> HANDLE {
} }
} }
} }
pub fn check_super_user_permission() -> ResultType<bool> {
unsafe {
let ret = ShellExecuteA(
NULL as _,
CString::new("runas")?.as_ptr() as _,
CString::new("cmd")?.as_ptr() as _,
CString::new("/c /q")?.as_ptr() as _,
NULL as _,
SW_SHOWNORMAL,
);
return Ok(ret as i32 > 32);
}
}

View File

@ -676,6 +676,13 @@ pub fn has_hwcodec() -> bool {
return true; return true;
} }
pub fn check_super_user_permission() -> bool {
#[cfg(any(windows, target_os = "linux"))]
return crate::platform::check_super_user_permission().unwrap_or(false);
#[cfg(not(any(windows, target_os = "linux")))]
true
}
pub fn check_zombie(childs: Childs) { pub fn check_zombie(childs: Childs) {
let mut deads = Vec::new(); let mut deads = Vec::new();
loop { loop {