From 111c6aa27113a22f4bf3725b63e65d1a6b4994b0 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 16 Jan 2024 08:37:41 -0400 Subject: [PATCH 1/3] fix: terminal sessions are automatically closed once an error occur when trying to ssh --- .../controllers/terminal_session_controller.dart | 13 ++++++++++--- .../profile_actions/profile_terminal_action.dart | 15 ++++++++------- .../terminal_screen_desktop_view.dart | 4 ++-- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/dart/sshnp_flutter/lib/src/controllers/terminal_session_controller.dart b/packages/dart/sshnp_flutter/lib/src/controllers/terminal_session_controller.dart index c4ce95791..0c9d333aa 100644 --- a/packages/dart/sshnp_flutter/lib/src/controllers/terminal_session_controller.dart +++ b/packages/dart/sshnp_flutter/lib/src/controllers/terminal_session_controller.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:developer'; import 'package:dartssh2/dartssh2.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -71,7 +72,8 @@ class TerminalSession { String displayName; bool isRunning = false; - bool isDisposed = true; + // TODO: delete this + // bool isDisposed = true; SshnpRemoteProcess? shell; @@ -104,7 +106,8 @@ class TerminalSessionFamilyController extends FamilyNotifier { if (mounted) { showProgress('Starting Shell Session...'); } + + /// Issue a new session id + final sessionId = ref.watch(terminalSessionController.notifier).createSession(); + + /// Create the session controller for the new session id + final sessionController = ref.watch(terminalSessionFamilyController(sessionId).notifier); // TODO: add try try { // TODO ensure that this keyPair gets uploaded to the app first @@ -104,17 +110,11 @@ class _ProfileTerminalActionState extends ConsumerState { throw result; } - /// Issue a new session id - final sessionId = ref.watch(terminalSessionController.notifier).createSession(); - - /// Create the session controller for the new session id - final sessionController = ref.watch(terminalSessionFamilyController(sessionId).notifier); - if (result is SshnpCommand) { if (sshnp.canRunShell) { if (mounted) { context.pop(); - showProgress('running shell session'); + showProgress('running shell session...'); } log('running shell session...'); @@ -139,6 +139,7 @@ class _ProfileTerminalActionState extends ConsumerState { } //TODO: Add catch } catch (e) { + sessionController.dispose(); if (mounted) { log('error: ${e.toString()}'); context.pop(); diff --git a/packages/dart/sshnp_flutter/lib/src/presentation/widgets/terminal_screen_widgets/terminal_screen_desktop_view.dart b/packages/dart/sshnp_flutter/lib/src/presentation/widgets/terminal_screen_widgets/terminal_screen_desktop_view.dart index 59c03a287..4d33c7540 100644 --- a/packages/dart/sshnp_flutter/lib/src/presentation/widgets/terminal_screen_widgets/terminal_screen_desktop_view.dart +++ b/packages/dart/sshnp_flutter/lib/src/presentation/widgets/terminal_screen_widgets/terminal_screen_desktop_view.dart @@ -24,7 +24,7 @@ class _TerminalScreenDesktopViewState extends ConsumerState terminalList) { + void closeSession(String sessionId) { // Remove the session from the list of sessions final controller = ref.read(terminalSessionFamilyController(sessionId).notifier); controller.dispose(); @@ -73,7 +73,7 @@ class _TerminalScreenDesktopViewState extends ConsumerState closeSession(sessionId, terminalList), + onPressed: () => closeSession(sessionId), ) ], ), From 9e162db06ddeb047376750df9558fcc69002a726 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 16 Jan 2024 11:45:17 -0400 Subject: [PATCH 2/3] fix: profile private key is deleted same time the related private key is deleted --- .../private_key_manager_controller.dart | 21 +++++++- .../profile_form_desktop_view.dart | 50 ++----------------- .../ssh_key_pair_bar_actions.dart | 19 +++---- 3 files changed, 30 insertions(+), 60 deletions(-) diff --git a/packages/dart/sshnp_flutter/lib/src/controllers/private_key_manager_controller.dart b/packages/dart/sshnp_flutter/lib/src/controllers/private_key_manager_controller.dart index 1bef3acfc..6f3dd1a37 100644 --- a/packages/dart/sshnp_flutter/lib/src/controllers/private_key_manager_controller.dart +++ b/packages/dart/sshnp_flutter/lib/src/controllers/private_key_manager_controller.dart @@ -1,12 +1,16 @@ import 'dart:async'; +import 'dart:developer'; +import 'package:at_client_mobile/at_client_mobile.dart'; import 'package:biometric_storage/biometric_storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:noports_core/sshnp_params.dart'; import 'package:sshnp_flutter/src/controllers/navigation_controller.dart'; import 'package:sshnp_flutter/src/presentation/widgets/utility/custom_snack_bar.dart'; import 'package:sshnp_flutter/src/repository/private_key_manager_repository.dart'; +import 'package:sshnp_flutter/src/repository/profile_private_key_manager_repository.dart'; import '../application/private_key_manager.dart'; import '../repository/navigation_repository.dart'; @@ -125,8 +129,23 @@ class AtSshKeyPairManagerFamilyController extends AutoDisposeFamilyAsyncNotifier Future deletePrivateKeyManager({required String identifier, BuildContext? context}) async { try { - await PrivateKeyManagerRepository.deletePrivateKeyManager(arg); + // await PrivateKeyManagerRepository.deletePrivateKeyManager(arg); ref.read(atPrivateKeyManagerListController.notifier).remove(arg); + // Read in profiles + AtClient atClient = AtClientManager.getInstance().atClient; + final profiles = await ConfigKeyRepository.listProfiles(atClient); + + for (final profile in profiles) { + //delete profile private key manager that matches the deleted private key manager + final profilePrivateKeyManager = await ProfilePrivateKeyManagerRepository.readProfilePrivateKeyManager(profile); + log('arg: $arg, profile $profile, profile private manager key nickname: ${profilePrivateKeyManager.privateKeyNickname}, profilePrivateKeyManager profile name: ${profilePrivateKeyManager.profileNickname}'); + + if (profilePrivateKeyManager.privateKeyNickname == arg) { + log('profile from if clause: $profile'); + await ProfilePrivateKeyManagerRepository.deleteProfilePrivateKeyManager(profile); + } + } + state = AsyncValue.error('SSHNPParams has been disposed', StackTrace.current); } catch (e) { if (context?.mounted ?? false) { diff --git a/packages/dart/sshnp_flutter/lib/src/presentation/widgets/profile_screen_widgets/profile_form/profile_form_desktop_view.dart b/packages/dart/sshnp_flutter/lib/src/presentation/widgets/profile_screen_widgets/profile_form/profile_form_desktop_view.dart index 07a16fb5e..55d087f7e 100644 --- a/packages/dart/sshnp_flutter/lib/src/presentation/widgets/profile_screen_widgets/profile_form/profile_form_desktop_view.dart +++ b/packages/dart/sshnp_flutter/lib/src/presentation/widgets/profile_screen_widgets/profile_form/profile_form_desktop_view.dart @@ -41,7 +41,9 @@ class _ProfileFormState extends ConsumerState { ref.read(formProfileNameController.notifier).state = currentProfile.profileName; privateKeyNickname = await ProfilePrivateKeyManagerRepository.readProfilePrivateKeyManager(currentProfile.profileName) - .then((value) => value?.privateKeyNickname); + .then((value) => value.privateKeyNickname); + + if (privateKeyNickname == '') privateKeyNickname = null; }); super.initState(); @@ -83,7 +85,6 @@ class _ProfileFormState extends ConsumerState { final strings = AppLocalizations.of(context)!; currentProfile = ref.watch(currentConfigController); final privateKeyManagerListController = ref.watch(atPrivateKeyManagerListController); - // final profilePrivateKeyListController = ref.watch(profilePrivateKeyManagerListController); final asyncOldConfig = ref.watch(configFamilyController(currentProfile.profileName)); @@ -279,55 +280,10 @@ class _ProfileFormState extends ConsumerState { }, onValidator: FormValidator.validatePrivateKeyField, ); - - // return CustomMultiSelectChipFormField( - // width: kFieldDefaultWidth + Sizes.p5, - // selectedItems: selectedItems, - // label: strings.privateKey, - // hintText: strings.select, - // items: privateKeyList.toList(), - // onChanged: (value) { - // if (value == kPrivateKeyDropDownOption) { - // showDialog( - // context: context, builder: ((context) => const SSHKeyManagementFormDialog())); - // } - // }, - // onSaved: (value) { - // final atSshKeyPair = ref.read(privateKeyManagerFamilyController(value!)); - // // write to a new controller to map the profile name to the ssh key pair manager nickname. Store this information locally. - - // // At the point of ssh connect the profile name and the key manager name so get the identity passphrase and the fiel content. - // atSshKeyPair.when( - // data: (data) => newConfig = SshnpPartialParams.merge( - // newConfig, - // SshnpPartialParams( - // identityFile: data.nickname, identityPassphrase: data.passPhrase)), - // error: ((error, stackTrace) => log(error.toString())), - // loading: () => const CircularProgressIndicator()); - // }, - // onValidator: FormValidator.validatePrivateKeyField, - // ); }), gapH20, - // TODO: Add key management dropdown here gapH20, - - // CustomDropdownFormField( - // label: strings.sshAlgorithm, - // hintText: strings.select, - // initialValue: oldConfig.sshAlgorithm, - // items: SupportedSshAlgorithm.values - // .map((e) => DropdownMenuItem( - // value: e, - // child: Text(e.name), - // )) - // .toList(), - // onChanged: ((value) => - // newConfig = SshnpPartialParams.merge(newConfig, SshnpPartialParams(sshAlgorithm: value))), - // ), - gapH10, CustomSwitchWidget( - //TODO: change string to sendSshPublicKey not ssh public key labelText: strings.sendSshPublicKey, value: newConfig.sendSshPublicKey ?? oldConfig.sendSshPublicKey, tooltip: strings.sendSshPublicKeyTooltip, diff --git a/packages/dart/sshnp_flutter/lib/src/presentation/widgets/ssh_key_management/ssh_key_pair_bar_actions.dart b/packages/dart/sshnp_flutter/lib/src/presentation/widgets/ssh_key_management/ssh_key_pair_bar_actions.dart index 7459726d4..4bb0141a7 100644 --- a/packages/dart/sshnp_flutter/lib/src/presentation/widgets/ssh_key_management/ssh_key_pair_bar_actions.dart +++ b/packages/dart/sshnp_flutter/lib/src/presentation/widgets/ssh_key_management/ssh_key_pair_bar_actions.dart @@ -7,26 +7,22 @@ import '../../../utility/constants.dart'; import 'ssh_key_management_form_dialog.dart'; class SshKeyPairBarActions extends ConsumerStatefulWidget { - const SshKeyPairBarActions({required this.identifier, Key? key}) - : super(key: key); + const SshKeyPairBarActions({required this.identifier, Key? key}) : super(key: key); final String identifier; @override - ConsumerState createState() => - _SshKeyPairBarActionsState(); + ConsumerState createState() => _SshKeyPairBarActionsState(); } class _SshKeyPairBarActionsState extends ConsumerState { @override Widget build(BuildContext context) { - final controller = ref - .watch(privateKeyManagerFamilyController(widget.identifier).notifier); + final controller = ref.watch(privateKeyManagerFamilyController(widget.identifier).notifier); return Row( children: [ IconButton( - style: - FilledButton.styleFrom(backgroundColor: kIconColorBackgroundDark), + style: FilledButton.styleFrom(backgroundColor: kIconColorBackgroundDark), onPressed: () async { await showDialog( context: context, @@ -41,10 +37,9 @@ class _SshKeyPairBarActionsState extends ConsumerState { ), gapW8, IconButton( - style: - FilledButton.styleFrom(backgroundColor: kIconColorBackgroundDark), - onPressed: () { - controller.deletePrivateKeyManager(identifier: widget.identifier); + style: FilledButton.styleFrom(backgroundColor: kIconColorBackgroundDark), + onPressed: () async { + await controller.deletePrivateKeyManager(identifier: widget.identifier); }, icon: const Icon( Icons.delete_outline, From 3c59ae3f66811861348a55cb72e0412f56c33d0f Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 16 Jan 2024 13:21:26 -0400 Subject: [PATCH 3/3] fix: profile name and private key nickname validator updated to prevent upper case letters. --- packages/dart/sshnp_flutter/lib/src/utility/constants.dart | 4 ++-- .../dart/sshnp_flutter/lib/src/utility/form_validator.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/dart/sshnp_flutter/lib/src/utility/constants.dart b/packages/dart/sshnp_flutter/lib/src/utility/constants.dart index c47a4242f..e24648e01 100644 --- a/packages/dart/sshnp_flutter/lib/src/utility/constants.dart +++ b/packages/dart/sshnp_flutter/lib/src/utility/constants.dart @@ -20,8 +20,8 @@ const kInputChipBackgroundColor = Color(0XFF515151); const kEmptyFieldValidationError = 'Field cannot be left blank'; const kAtsignFieldValidationError = 'Field must start with @'; -const kProfileNameFieldValidationError = 'Field must only use alphanumeric characters and spaces'; -const kPrivateKeyFieldValidationError = 'Field must only use alphanumeric characters'; +const kProfileNameFieldValidationError = 'Field must only use lower case alphanumeric characters spaces'; +const kPrivateKeyFieldValidationError = 'Field must only use lower case alphanumeric characters'; const kPrivateKeyDropDownOption = 'Create a new private key'; diff --git a/packages/dart/sshnp_flutter/lib/src/utility/form_validator.dart b/packages/dart/sshnp_flutter/lib/src/utility/form_validator.dart index 8ba9db8ba..7804a9c5e 100644 --- a/packages/dart/sshnp_flutter/lib/src/utility/form_validator.dart +++ b/packages/dart/sshnp_flutter/lib/src/utility/form_validator.dart @@ -20,7 +20,7 @@ class FormValidator { } static String? validateProfileNameField(String? value) { - String invalid = '[^a-zA-Z0-9 ]'; + String invalid = '[^a-z0-9 ]'; if (value?.isEmpty ?? true) { return kEmptyFieldValidationError; } else if (value!.contains(RegExp(invalid))) { @@ -30,7 +30,7 @@ class FormValidator { } static String? validatePrivateKeyField(String? value) { - String invalid = '[^a-zA-Z0-9_]'; + String invalid = '[^a-z0-9_]'; if (value?.isEmpty ?? true) { return kEmptyFieldValidationError; } else if (value! == kPrivateKeyDropDownOption) {