mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-11-04 00:33:12 +00:00
feat: add connections without sending passwords
Add Without password actions and pass the selection through desktop, mobile and Web sessions. Omit the password when constructing LoginRequest, preserve saved credentials, and reuse the existing approval and OS login dialogs. Keep protocol and controlled-side authentication changes separate.
This commit is contained in:
1 parent
ef827fdb34
commit
956031d109
87 files changed
+853
-23
No files matched your search
@@ -0,0 +1,146 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_hbb/common/widgets/login_method_menu.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
|
||||
as mod_menu;
|
||||
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
|
||||
|
||||
Future<void> openMenu(WidgetTester tester, List<String> actions) async {
|
||||
MenuEntryButton<String> entry(String title) => MenuEntryButton<String>(
|
||||
childBuilder: (style) => Text(title, style: style),
|
||||
proc: () => actions.add(title),
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Builder(builder: (context) {
|
||||
return TextButton(
|
||||
onPressed: () => mod_menu.showMenu<String>(
|
||||
context: context,
|
||||
position: const RelativeRect.fromLTRB(20, 20, 780, 580),
|
||||
items: [
|
||||
LoginMethodMenu(
|
||||
defaultEntry: entry('Connect'),
|
||||
methods: [entry('Without password')],
|
||||
),
|
||||
LoginMethodMenu(
|
||||
defaultEntry: entry('View camera'),
|
||||
methods: [entry('Without password')],
|
||||
),
|
||||
LoginMethodMenu(
|
||||
defaultEntry: MenuEntryButton<String>(
|
||||
childBuilder: (style) => Row(children: [
|
||||
Text('RDP', style: style),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
actions.add('Edit RDP');
|
||||
},
|
||||
),
|
||||
]),
|
||||
proc: () => actions.add('RDP'),
|
||||
),
|
||||
methods: [entry('Without password')],
|
||||
),
|
||||
entry('Rename'),
|
||||
]
|
||||
.expand((entry) => entry.build(context,
|
||||
const MenuConfig(commonColor: Colors.black, height: 20)))
|
||||
.toList(),
|
||||
),
|
||||
child: const Text('Open menu'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
));
|
||||
await tester.tap(find.text('Open menu'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('main rows align and clicking them connects by default',
|
||||
(tester) async {
|
||||
for (final title in ['Connect', 'View camera', 'RDP']) {
|
||||
final actions = <String>[];
|
||||
await openMenu(tester, actions);
|
||||
final left = tester.getTopLeft(find.text('Rename')).dx;
|
||||
for (final row in ['Connect', 'View camera', 'RDP']) {
|
||||
expect(tester.getTopLeft(find.text(row)).dx, left);
|
||||
}
|
||||
await tester.tap(find.text(title));
|
||||
await tester.pumpAndSettle();
|
||||
expect(actions, [title]);
|
||||
expect(find.text('Without password'), findsNothing);
|
||||
expect(find.text('Rename'), findsNothing);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'arrow offers connection without password and dismisses both menus',
|
||||
(tester) async {
|
||||
final actions = <String>[];
|
||||
await openMenu(tester, actions);
|
||||
await tester.tap(find.byIcon(Icons.keyboard_arrow_right).first);
|
||||
await tester.pumpAndSettle();
|
||||
expect(actions, isEmpty);
|
||||
expect(find.text('Without password'), findsOneWidget);
|
||||
expect(find.text('Enter password manually'), findsNothing);
|
||||
await tester.tap(find.text('Without password'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(actions, ['Without password']);
|
||||
expect(find.text('Without password'), findsNothing);
|
||||
expect(find.text('Rename'), findsNothing);
|
||||
},
|
||||
variant: TargetPlatformVariant({
|
||||
TargetPlatform.android,
|
||||
TargetPlatform.iOS,
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.macOS,
|
||||
TargetPlatform.windows,
|
||||
}));
|
||||
|
||||
testWidgets('RDP edit remains independent of the connection action',
|
||||
(tester) async {
|
||||
final actions = <String>[];
|
||||
await openMenu(tester, actions);
|
||||
await tester.tap(find.byIcon(Icons.edit));
|
||||
await tester.pumpAndSettle();
|
||||
expect(actions, ['Edit RDP']);
|
||||
expect(find.text('Rename'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('touches around the arrow open the submenu without connecting',
|
||||
(tester) async {
|
||||
tester.view.physicalSize = const Size(360, 640);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
for (final corner in [
|
||||
Alignment.topLeft,
|
||||
Alignment.topRight,
|
||||
Alignment.bottomLeft,
|
||||
Alignment.bottomRight,
|
||||
]) {
|
||||
final actions = <String>[];
|
||||
await openMenu(tester, actions);
|
||||
final arrow = find.byIcon(Icons.keyboard_arrow_right).first;
|
||||
final row = tester
|
||||
.getRect(find.ancestor(of: arrow, matching: find.byType(Row)).first);
|
||||
final target = Rect.fromCenter(
|
||||
center: tester.getCenter(arrow), width: 56, height: row.height);
|
||||
await tester.tapAt(corner.withinRect(target.deflate(2)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(actions, isEmpty);
|
||||
expect(find.text('Without password'), findsOneWidget);
|
||||
await tester.tap(find.text('Without password'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(actions, ['Without password']);
|
||||
expect(find.text('Rename'), findsNothing);
|
||||
}
|
||||
},
|
||||
variant:
|
||||
TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('without-password links preserve their connection type', () {
|
||||
for (final type in [
|
||||
'connect',
|
||||
'play',
|
||||
'file-transfer',
|
||||
'view-camera',
|
||||
'port-forward',
|
||||
'rdp',
|
||||
'terminal',
|
||||
'terminal-admin',
|
||||
]) {
|
||||
expect(
|
||||
urlLinkToCmdArgs(
|
||||
Uri.parse('rustdesk://$type/123456789?without-password=true')),
|
||||
['--$type', '123456789', '--without-password'],
|
||||
);
|
||||
}
|
||||
expect(
|
||||
urlLinkToCmdArgs(Uri.parse('rustdesk://123456789?without-password=true')),
|
||||
['--connect', '123456789', '--without-password'],
|
||||
);
|
||||
});
|
||||
|
||||
test('omitted and empty passwords retain automatic login', () {
|
||||
expect(urlLinkToCmdArgs(Uri.parse('rustdesk://123456789')),
|
||||
['--connect', '123456789']);
|
||||
for (final query in ['password=', 'password=&without-password=false']) {
|
||||
expect(urlLinkToCmdArgs(Uri.parse('rustdesk://123456789?$query')),
|
||||
['--connect', '123456789', '--password', '']);
|
||||
}
|
||||
});
|
||||
|
||||
test('without-password ignores an explicit password in either order', () {
|
||||
for (final query in [
|
||||
'password=provided&without-password=true',
|
||||
'without-password=true&password=provided',
|
||||
]) {
|
||||
expect(urlLinkToCmdArgs(Uri.parse('rustdesk://123456789?$query')),
|
||||
['--connect', '123456789', '--without-password']);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user