-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: sshnp gui core MVP #467
Merged
Changes from all commits
Commits
Show all changes
113 commits
Select commit
Hold shift + click to select a range
d380637
fix: home screen and new config screen refactored to use sshnoports b…
CurtlyCritchlow eee0b31
chore: clean up sshnp params and implement merge and empty on the ful…
XavierChanth 3fdac47
feat: implement sshnp config controller family
XavierChanth d57ff86
refactor: add sshnp params controller
XavierChanth 93c4ead
fix: bugs due to previous refactoring
XavierChanth 5ade3c7
chore: cleanup naming
XavierChanth 28ebc14
chore: make sshnp partial params all final again
XavierChanth 5a53354
Merge pull request #424 from atsign-foundation/fix/update-ssh-gui-xavier
CurtlyCritchlow b14259f
chore: reorganize widgets folder
XavierChanth aadcb27
chore: rename connection form to profile form
XavierChanth a2d3a87
chore: rename profile actions
XavierChanth f7cf687
chore: rename profile actions
XavierChanth dc4eccb
chore: update profile_actions
XavierChanth fad4ade
chore: rename profile form
XavierChanth 6d8f87b
chore: rename profile form
XavierChanth 05585b8
fix: profileName with spaces
XavierChanth 5cab0d1
chore: ignore '.env'
XavierChanth 91a3386
chore: clean up profile bar and profile form
XavierChanth 9574146
chore: rename profile editor screen
XavierChanth 7228445
chore: organize imports
XavierChanth d0cd618
chore: cleanup profile_form
XavierChanth 3b94ad7
chore: cleanup navigation controller
XavierChanth c41ba32
chore: cleanup naming for params controllers
XavierChanth 4036285
chore: reorder sshnp params controller
XavierChanth 955cf3a
chore: cleanup sshnp params controller
XavierChanth 06fccfc
feat: add terminal session controller
XavierChanth 778ef9d
chore: cleanup some bugs
XavierChanth 6c68865
chore: cleanup logging
XavierChanth b885e5c
fix: nav rail for non rail routes
XavierChanth a036fba
chore: final cleanup
XavierChanth 23a75bc
feat: show the command being run by the terminal
XavierChanth 4065e3e
chore: no autodispose on terminal controllers
XavierChanth 1a17bca
feat: sort home screen profiles
XavierChanth 0c7ae84
feat: default 10000 max lines for the terminal
XavierChanth 8f08b9a
Merge pull request #432 from atsign-foundation/gui/xavierchanth-07-09…
CurtlyCritchlow e83e623
feat: new terminal shown on terminal screen when terminal icon is pre…
CurtlyCritchlow f58bc71
Merge branch 'feat/multiple-terminal' into sshnp-gui
XavierChanth 24ef186
refactor: create settings actions
XavierChanth cc424db
deps: cleanup dependencies
XavierChanth 10d5144
refactor: nav rail controller
XavierChanth be53df0
refactor: navigation and terminal tabs
XavierChanth 1de21a2
chore: cleanup imports
XavierChanth 71c0b81
fix: test widget pumper
XavierChanth 3ed6ccc
feat: add background session controller
XavierChanth ab08f4f
chore: remove async from initState
XavierChanth 2814ab4
docs: note the changes required for run_action
XavierChanth 826c9a7
chore: disable run action for now
XavierChanth 416868c
chore: add no-sessions prompt for terminal screen
XavierChanth 37c46a0
feat: add callback for terminal tab to close on process exit
XavierChanth ac6c88b
chore: cleanup unused imports
XavierChanth a318e1f
style: make home title larger
XavierChanth b464dbc
chore: sort localizations
XavierChanth 854ce83
fix: backup keys string
XavierChanth 03906c1
Merge branch 'fix/sendSshPublicKey' into sshnp-gui
XavierChanth a2e3b94
fix: use SSHNPD.namespace as the default namespace for sshnp_gui
XavierChanth b0f44e0
Merge pull request #440 from atsign-foundation/fix/gui-namespace
gkc 3a8fed6
feat: cleanup terminal behaviors
XavierChanth 98a977f
Merge branch 'gui-terminal-behaviour' into sshnp-gui
XavierChanth e6888d9
chore: make cursorInvisible when doing exit countdown
XavierChanth 01b6ed7
fix: minimum desktop size set other minor changes to all correct sizi…
CurtlyCritchlow 0b25b1d
Merge pull request #447 from atsign-foundation/fix/sizing
XavierChanth 10ed463
Merge remote-tracking branch 'origin/trunk' into sshnp-gui
XavierChanth 0780a45
chore: rename sshnpparams to config in controller
XavierChanth 65a52b1
chore: rename sshnp_params_controller.dart to config_controller.dart
XavierChanth 3bc8be7
feat: add ConfigSource and covariants
XavierChanth 9cee993
format: sort members of sshnp_params.dart
XavierChanth 5c0ec6d
chore: move config_source to sshnoports package
XavierChanth 3b3ddaa
chore: more cleanup
XavierChanth 9b8332c
chore: checkin config file management
XavierChanth 29bee9c
chore: revert configFamilyController
XavierChanth 4a19820
feat: add config_repos to sshnoports core
XavierChanth ff3ce17
Merge remote-tracking branch 'origin/trunk' into gui/config-syncing
XavierChanth 6ce836a
feat: allow deletion of corrupted configs
XavierChanth 4327a4c
feat: implement profile menu button
XavierChanth 98df3dc
chore: rename profile action button file
XavierChanth 2118f4d
fix: Add macOS UTI specification for atKeys files
XavierChanth 8b51edd
Merge remote-tracking branch 'origin/sshnp-gui' into gui/config-syncing
XavierChanth f8bea7c
fix: ensure config files are properly formed when putting them
XavierChanth 85861f1
Merge remote-tracking branch 'origin/trunk' into sshnp-gui
XavierChanth 3019bbb
Merge branch 'sshnp-gui' into gui/config-syncing
XavierChanth 505573e
fix: use ConfigFileRepository helper function
XavierChanth d55ef78
fix: set clientAtSign within the constructor for SSHNPPartialParams
XavierChanth c843680
Merge branch 'gui/config-syncing' into sshnp-gui
XavierChanth 7f74a2b
feat: implement idleTimeout and addForwardsToTunnel for Run action
XavierChanth 221dcb4
feat: add export option
XavierChanth 1ba1152
feat: add import profile feature
XavierChanth 34347b8
Merge branch 'gui/config-syncing' into sshnp-gui
XavierChanth 2cee553
Merge branch 'sshnp-gui' of github.com:atsign-foundation/sshnoports i…
XavierChanth 5b136aa
Merge branch 'gui/config-syncing' into sshnp-gui
XavierChanth 6b1db7a
Merge branch 'trunk' into sshnp-gui
XavierChanth 7e435ff
feat: add background ssh session
XavierChanth 31f4a02
chore: remove unused import
XavierChanth d4d16df
chore: cleanup
XavierChanth 8e3d87c
chore: cleanup error messages
XavierChanth a2f2554
chore: change 3.5.0 references to 3.4.0
XavierChanth d93aeab
chore: add a hint to the SSHNPFailed for daemon timeouts
XavierChanth ac49b06
feat: add localSshOptions to SSHCommand.base
XavierChanth 9885b79
Merge remote-tracking branch 'origin/trunk' into sshnp-gui
XavierChanth 387ddbf
chore: set version to 4.0.0-rc.4
XavierChanth 5d2a4c6
fix: resize computation
XavierChanth 2ab9798
feat: force legacy daemon to false
XavierChanth 0d4879a
feat: improve form consistency
XavierChanth 651fef4
fix: better handle failed profile editing
XavierChanth 1336e4a
fix: note that legacy daemons are actually <4.0.0 not <3.4.0
XavierChanth 2aac517
fix: normalize all paths
XavierChanth ee2da0e
format: dart format
XavierChanth 6890368
feat: use pure-dart clients
XavierChanth f5df86c
format: dart format
XavierChanth e8359fe
fix: flip addForwardsToTunnel
XavierChanth 831babe
chore: update pubspec.lock
XavierChanth 016dd2c
chore: increase version
XavierChanth aa0c591
fix: use stricter form validation
XavierChanth 072adf9
fix: profileName validator
XavierChanth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ import 'dart:io'; | |
import 'package:at_chops/at_chops.dart'; | ||
import 'package:at_client/at_client.dart'; | ||
import 'package:at_utils/at_utils.dart'; | ||
import 'package:path/path.dart' as path; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. file normalization in this file too |
||
/// Get the home directory or null if unknown. | ||
String? getHomeDirectory({bool throwIfNull = false}) { | ||
|
@@ -56,20 +57,19 @@ bool checkNonAscii(String test) { | |
|
||
String getDefaultAtKeysFilePath(String homeDirectory, String? atSign) { | ||
if (atSign == null) return ''; | ||
return '$homeDirectory/.atsign/keys/${atSign}_key.atKeys' | ||
.replaceAll('/', Platform.pathSeparator); | ||
return path.normalize('$homeDirectory/.atsign/keys/${atSign}_key.atKeys'); | ||
} | ||
|
||
String getDefaultSshDirectory(String homeDirectory) { | ||
return '$homeDirectory/.ssh/'.replaceAll('/', Platform.pathSeparator); | ||
return path.normalize('$homeDirectory/.ssh/'); | ||
} | ||
|
||
String getDefaultSshnpDirectory(String homeDirectory) { | ||
return '$homeDirectory/.sshnp/'.replaceAll('/', Platform.pathSeparator); | ||
return path.normalize('$homeDirectory/.sshnp/'); | ||
} | ||
|
||
String getDefaultSshnpConfigDirectory(String homeDirectory) { | ||
return '$homeDirectory/.sshnp/config'.replaceAll('/', Platform.pathSeparator); | ||
return path.normalize('$homeDirectory/.sshnp/config'); | ||
} | ||
|
||
/// Checks if the provided atSign's atServer has been properly activated with a public RSA key. | ||
|
@@ -144,9 +144,9 @@ Future<(String, String)> generateSshKeys( | |
} | ||
|
||
String sshPublicKey = | ||
await File('$sshHomeDirectory${sessionId}_sshnp.pub').readAsString(); | ||
await File('$sshHomeDirectory/${sessionId}_sshnp.pub').readAsString(); | ||
String sshPrivateKey = | ||
await File('$sshHomeDirectory${sessionId}_sshnp').readAsString(); | ||
await File('$sshHomeDirectory/${sessionId}_sshnp').readAsString(); | ||
|
||
return (sshPublicKey, sshPrivateKey); | ||
} | ||
|
@@ -162,16 +162,15 @@ Future<void> addEphemeralKeyToAuthorizedKeys( | |
} | ||
|
||
String homeDirectory = getHomeDirectory(throwIfNull: true)!; | ||
var sshHomeDirectory = getDefaultSshDirectory(homeDirectory); | ||
|
||
var sshHomeDirectory = | ||
'$homeDirectory/.ssh/'.replaceAll('/', Platform.pathSeparator); | ||
if (!Directory(sshHomeDirectory).existsSync()) { | ||
Directory(sshHomeDirectory).createSync(); | ||
} | ||
|
||
// Check to see if the ssh Publickey is already in the authorized_keys file. | ||
// If not, then append it. | ||
var authKeys = File('${sshHomeDirectory}authorized_keys'); | ||
var authKeys = File(path.normalize('$sshHomeDirectory/authorized_keys')); | ||
|
||
var authKeysContent = await authKeys.readAsString(); | ||
if (!authKeysContent.endsWith('\n')) { | ||
|
@@ -184,14 +183,16 @@ Future<void> addEphemeralKeyToAuthorizedKeys( | |
} | ||
// Set up a safe authorized_keys file, for the ssh tunnel | ||
await authKeys.writeAsString( | ||
'command="echo \\"ssh session complete\\";sleep 20"' | ||
',PermitOpen="localhost:$localSshdPort"' | ||
'$permissions' | ||
' ' | ||
'${sshPublicKey.trim()}' | ||
' ' | ||
'sshnp_ephemeral_$sessionId\n', | ||
mode: FileMode.append); | ||
'command="echo \\"ssh session complete\\";sleep 20"' | ||
',PermitOpen="localhost:$localSshdPort"' | ||
'$permissions' | ||
' ' | ||
'${sshPublicKey.trim()}' | ||
' ' | ||
'sshnp_ephemeral_$sessionId\n', | ||
mode: FileMode.append, | ||
flush: true, | ||
); | ||
} | ||
} | ||
|
||
|
@@ -201,7 +202,7 @@ Future<void> removeEphemeralKeyFromAuthorizedKeys( | |
try { | ||
sshHomeDirectory ??= | ||
getDefaultSshDirectory(getHomeDirectory(throwIfNull: true)!); | ||
final File file = File('${sshHomeDirectory}authorized_keys'); | ||
final File file = File(path.normalize('$sshHomeDirectory/authorized_keys')); | ||
logger.info('Removing ephemeral key for session $sessionId' | ||
' from ${file.absolute.path}'); | ||
// read into List of strings | ||
|
@@ -212,7 +213,8 @@ Future<void> removeEphemeralKeyFromAuthorizedKeys( | |
await file.writeAsString(lines.join('\n')); | ||
await file.writeAsString('\n', mode: FileMode.writeOnlyAppend); | ||
} catch (e) { | ||
logger.severe('Unable to tidy up ${sshHomeDirectory}authorized_keys'); | ||
logger.severe( | ||
'Unable to tidy up ${path.normalize('$sshHomeDirectory/authorized_keys')}'); | ||
} | ||
} | ||
|
||
|
@@ -289,9 +291,8 @@ Future<String?> _fetchFromLocalPKCache( | |
AtClient atClient, String atSign, bool useFileStorage) async { | ||
String dontAtMe = atSign.substring(1); | ||
if (useFileStorage) { | ||
String fn = '${getHomeDirectory(throwIfNull: true)}' | ||
'/.atsign/sshnp/cached_pks/$dontAtMe' | ||
.replaceAll('/', Platform.pathSeparator); | ||
String fn = path.normalize( | ||
'${getHomeDirectory(throwIfNull: true)}/.atsign/sshnp/cached_pks/$dontAtMe'); | ||
File f = File(fn); | ||
if (await f.exists()) { | ||
return (await f.readAsString()).trim(); | ||
|
@@ -314,11 +315,9 @@ Future<bool> _storeToLocalPKCache( | |
String pk, AtClient atClient, String atSign, bool useFileStorage) async { | ||
String dontAtMe = atSign.substring(1); | ||
if (useFileStorage) { | ||
String dirName = | ||
'${getHomeDirectory(throwIfNull: true)}/.atsign/sshnp/cached_pks' | ||
.replaceAll('/', Platform.pathSeparator); | ||
String fileName = | ||
'$dirName/$dontAtMe'.replaceAll('/', Platform.pathSeparator); | ||
String dirName = path.normalize( | ||
'${getHomeDirectory(throwIfNull: true)}/.atsign/sshnp/cached_pks'); | ||
String fileName = path.normalize('$dirName/$dontAtMe'); | ||
|
||
File f = File(fileName); | ||
if (!await f.exists()) { | ||
|
152 changes: 152 additions & 0 deletions
152
packages/sshnoports/lib/sshnp/config_repository/config_file_repository.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:sshnoports/common/utils.dart'; | ||
import 'package:sshnoports/sshnp/sshnp.dart'; | ||
import 'package:sshnoports/sshnp/sshnp_arg.dart'; | ||
import 'package:path/path.dart' as path; | ||
|
||
class ConfigFileRepository { | ||
static String toProfileName(String fileName, {bool replaceSpaces = true}) { | ||
var profileName = path.basenameWithoutExtension(fileName); | ||
if (replaceSpaces) profileName = profileName.replaceAll('_', ' '); | ||
return profileName; | ||
} | ||
|
||
static String fromProfileName(String profileName, | ||
{String? directory, bool replaceSpaces = true, bool basenameOnly = false}) { | ||
var fileName = profileName; | ||
if (replaceSpaces) fileName = fileName.replaceAll(' ', '_'); | ||
final basename = '$fileName.env'; | ||
if (basenameOnly) return basename; | ||
return path.join( | ||
directory ?? getDefaultSshnpConfigDirectory(getHomeDirectory(throwIfNull: true)!), | ||
basename, | ||
); | ||
} | ||
|
||
static Future<Directory> createConfigDirectory({String? directory}) async { | ||
directory ??= getDefaultSshnpConfigDirectory(getHomeDirectory(throwIfNull: true)!); | ||
var dir = Directory(directory); | ||
if (!await dir.exists()) { | ||
await dir.create(recursive: true); | ||
} | ||
return dir; | ||
} | ||
|
||
static Future<Iterable<String>> listProfiles({String? directory}) async { | ||
var profileNames = <String>{}; | ||
|
||
var homeDirectory = getHomeDirectory(throwIfNull: true)!; | ||
directory ??= getDefaultSshnpConfigDirectory(homeDirectory); | ||
var files = Directory(directory).list(); | ||
|
||
await files.forEach((file) { | ||
if (file is! File) return; | ||
if (path.extension(file.path) != '.env') return; | ||
if (path.basenameWithoutExtension(file.path).isEmpty) return; // ignore '.env' file - empty profileName | ||
profileNames.add(toProfileName(file.path)); | ||
}); | ||
return profileNames; | ||
} | ||
|
||
static Future<SSHNPParams> getParams(String profileName, {String? directory}) async { | ||
var fileName = fromProfileName(profileName, directory: directory); | ||
return SSHNPParams.fromFile(fileName); | ||
} | ||
|
||
static Future<File> putParams(SSHNPParams params, {String? directory, bool overwrite = false}) async { | ||
if (params.profileName == null || params.profileName!.isEmpty) { | ||
throw Exception('profileName is null or empty'); | ||
} | ||
|
||
var fileName = fromProfileName(params.profileName!, directory: directory); | ||
var file = File(fileName); | ||
|
||
var exists = await file.exists(); | ||
|
||
if (exists && !overwrite) { | ||
throw Exception('Failed to write config file: ${file.path} already exists'); | ||
} | ||
|
||
// FileMode.write will create the file if it does not exist | ||
// and overwrite existing files if it does exist | ||
return file.writeAsString(params.toConfig(), mode: FileMode.write); | ||
} | ||
|
||
static Future<FileSystemEntity> deleteParams(SSHNPParams params, {String? directory}) async { | ||
if (params.profileName == null || params.profileName!.isEmpty) { | ||
throw Exception('profileName is null or empty'); | ||
} | ||
|
||
var fileName = fromProfileName(params.profileName!, directory: directory); | ||
var file = File(fileName); | ||
|
||
var exists = await file.exists(); | ||
|
||
if (!exists) { | ||
throw Exception('Cannot delete ${file.path}, file does not exist'); | ||
} | ||
|
||
return file.delete(); | ||
} | ||
|
||
static Map<String, dynamic> parseConfigFile(String fileName) { | ||
File file = File(fileName); | ||
|
||
if (!file.existsSync()) { | ||
throw Exception('Config file does not exist: $fileName'); | ||
} | ||
try { | ||
List<String> lines = file.readAsLinesSync(); | ||
return parseConfigFileContents(lines); | ||
} on FileSystemException { | ||
throw Exception('Error reading config file: $fileName'); | ||
} | ||
} | ||
|
||
static Map<String, dynamic> parseConfigFileContents(List<String> lines) { | ||
Map<String, dynamic> args = <String, dynamic>{}; | ||
|
||
try { | ||
for (String line in lines) { | ||
if (line.startsWith('#')) continue; | ||
|
||
var parts = line.split('='); | ||
if (parts.length != 2) continue; | ||
|
||
var key = parts[0].trim(); | ||
var value = parts[1].trim(); | ||
|
||
SSHNPArg arg = SSHNPArg.fromBashName(key); | ||
if (arg.name.isEmpty) continue; | ||
|
||
switch (arg.format) { | ||
case ArgFormat.flag: | ||
if (value.toLowerCase() == 'true') { | ||
args[arg.name] = true; | ||
} | ||
continue; | ||
case ArgFormat.multiOption: | ||
var values = value.split(','); | ||
args.putIfAbsent(arg.name, () => <String>[]); | ||
for (String val in values) { | ||
if (val.isEmpty) continue; | ||
args[arg.name].add(val); | ||
} | ||
continue; | ||
case ArgFormat.option: | ||
if (value.isEmpty) continue; | ||
if (arg.type == ArgType.integer) { | ||
args[arg.name] = int.tryParse(value); | ||
} else { | ||
args[arg.name] = value; | ||
} | ||
continue; | ||
} | ||
} | ||
return args; | ||
} catch (e) { | ||
throw Exception('Error parsing config file'); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed how paths are normalized in this file