From b5e67b68a826a9a7e32e107c782ad0fdda60ab48 Mon Sep 17 00:00:00 2001 From: gkc Date: Wed, 22 Nov 2023 14:24:52 +0000 Subject: [PATCH 1/2] feat: Add -U option to provide a `tunnelUsername` to use for the initial ssh tunnel. When not supplied, the username for the initial tunnel is the `remoteUsername` --- .../lib/src/sshnp/models/sshnp_arg.dart | 6 +++ .../lib/src/sshnp/models/sshnp_params.dart | 29 ++++++++----- .../lib/src/sshnp/sshnp_core.dart | 6 +++ .../sshnp_dart_initial_tunnel_handler.dart | 7 ++-- .../sshnp_openssh_initial_tunnel_handler.dart | 2 +- .../util/sshnpd_channel/sshnpd_channel.dart | 11 +++++ .../test/sshnp/models/sshnp_params_test.dart | 42 +++++++++++++++++++ .../test/sshnp/sshnp_core_test.dart | 2 + .../sshnp_local_ssh_key_handler_test.dart | 2 + packages/sshnp_gui/lib/l10n/app_en.arb | 1 + 10 files changed, 94 insertions(+), 14 deletions(-) diff --git a/packages/noports_core/lib/src/sshnp/models/sshnp_arg.dart b/packages/noports_core/lib/src/sshnp/models/sshnp_arg.dart index 84c62cbfe..4226b7257 100644 --- a/packages/noports_core/lib/src/sshnp/models/sshnp_arg.dart +++ b/packages/noports_core/lib/src/sshnp/models/sshnp_arg.dart @@ -115,6 +115,7 @@ class SshnpArg { localSshOptionsArg, verboseArg, remoteUserNameArg, + tunnelUserNameArg, rootDomainArg, localSshdPortArg, legacyDaemonArg, @@ -278,6 +279,11 @@ class SshnpArg { abbr: 'u', help: 'username to use in the ssh session on the remote host', ); + static const tunnelUserNameArg = SshnpArg( + name: 'tunnel-user-name', + abbr: 'U', + help: 'username to use for the initial ssh tunnel', + ); static const rootDomainArg = SshnpArg( name: 'root-domain', help: 'atDirectory domain', diff --git a/packages/noports_core/lib/src/sshnp/models/sshnp_params.dart b/packages/noports_core/lib/src/sshnp/models/sshnp_params.dart index 43006dc67..be37b9945 100644 --- a/packages/noports_core/lib/src/sshnp/models/sshnp_params.dart +++ b/packages/noports_core/lib/src/sshnp/models/sshnp_params.dart @@ -25,6 +25,7 @@ class SshnpParams { final bool sendSshPublicKey; final List localSshOptions; final String? remoteUsername; + final String? tunnelUsername; final bool verbose; final String rootDomain; final int localSshdPort; @@ -38,7 +39,7 @@ class SshnpParams { /// Special Arguments final String? - profileName; // automatically populated with the filename if from a configFile + profileName; // automatically populated with the filename if from a configFile /// Operation flags final bool listDevices; @@ -57,6 +58,7 @@ class SshnpParams { this.localSshOptions = DefaultSshnpArgs.localSshOptions, this.verbose = DefaultArgs.verbose, this.remoteUsername, + this.tunnelUsername, this.atKeysFilePath, this.rootDomain = DefaultArgs.rootDomain, this.localSshdPort = DefaultArgs.localSshdPort, @@ -94,10 +96,11 @@ class SshnpParams { atKeysFilePath: params2.atKeysFilePath ?? params1.atKeysFilePath, identityFile: params2.identityFile ?? params1.identityFile, identityPassphrase: - params2.identityPassphrase ?? params1.identityPassphrase, + params2.identityPassphrase ?? params1.identityPassphrase, sendSshPublicKey: params2.sendSshPublicKey ?? params1.sendSshPublicKey, localSshOptions: params2.localSshOptions ?? params1.localSshOptions, remoteUsername: params2.remoteUsername ?? params1.remoteUsername, + tunnelUsername: params2.tunnelUsername ?? params1.tunnelUsername, verbose: params2.verbose ?? params1.verbose, rootDomain: params2.rootDomain ?? params1.rootDomain, localSshdPort: params2.localSshdPort ?? params1.localSshdPort, @@ -106,7 +109,7 @@ class SshnpParams { remoteSshdPort: params2.remoteSshdPort ?? params1.remoteSshdPort, idleTimeout: params2.idleTimeout ?? params1.idleTimeout, addForwardsToTunnel: - params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, + params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, sshClient: params2.sshClient ?? params1.sshClient, sshAlgorithm: params2.sshAlgorithm ?? params1.sshAlgorithm, ); @@ -134,11 +137,12 @@ class SshnpParams { identityFile: partial.identityFile, identityPassphrase: partial.identityPassphrase, sendSshPublicKey: - partial.sendSshPublicKey ?? DefaultSshnpArgs.sendSshPublicKey, + partial.sendSshPublicKey ?? DefaultSshnpArgs.sendSshPublicKey, localSshOptions: - partial.localSshOptions ?? DefaultSshnpArgs.localSshOptions, + partial.localSshOptions ?? DefaultSshnpArgs.localSshOptions, verbose: partial.verbose ?? DefaultArgs.verbose, remoteUsername: partial.remoteUsername, + tunnelUsername: partial.tunnelUsername, atKeysFilePath: partial.atKeysFilePath, rootDomain: partial.rootDomain ?? DefaultArgs.rootDomain, localSshdPort: partial.localSshdPort ?? DefaultArgs.localSshdPort, @@ -147,7 +151,7 @@ class SshnpParams { remoteSshdPort: partial.remoteSshdPort ?? DefaultArgs.remoteSshdPort, idleTimeout: partial.idleTimeout ?? DefaultArgs.idleTimeout, addForwardsToTunnel: - partial.addForwardsToTunnel ?? DefaultArgs.addForwardsToTunnel, + partial.addForwardsToTunnel ?? DefaultArgs.addForwardsToTunnel, sshClient: partial.sshClient ?? DefaultSshnpArgs.sshClient, sshAlgorithm: partial.sshAlgorithm ?? DefaultArgs.sshAlgorithm, ); @@ -190,6 +194,7 @@ class SshnpParams { SshnpArg.sendSshPublicKeyArg.name: sendSshPublicKey, SshnpArg.localSshOptionsArg.name: localSshOptions, SshnpArg.remoteUserNameArg.name: remoteUsername, + SshnpArg.tunnelUserNameArg.name: tunnelUsername, SshnpArg.verboseArg.name: verbose, SshnpArg.rootDomainArg.name: rootDomain, SshnpArg.localSshdPortArg.name: localSshdPort, @@ -200,7 +205,7 @@ class SshnpParams { SshnpArg.sshAlgorithmArg.name: sshAlgorithm.toString(), }; args.removeWhere( - (key, value) => !parserType.shouldParse(SshnpArg.fromName(key).parseWhen), + (key, value) => !parserType.shouldParse(SshnpArg.fromName(key).parseWhen), ); return args; } @@ -229,6 +234,7 @@ class SshnpPartialParams { final bool? sendSshPublicKey; final List? localSshOptions; final String? remoteUsername; + final String? tunnelUsername; final bool? verbose; final String? rootDomain; final bool? legacyDaemon; @@ -255,6 +261,7 @@ class SshnpPartialParams { this.sendSshPublicKey, this.localSshOptions, this.remoteUsername, + this.tunnelUsername, this.verbose, this.rootDomain, this.localSshdPort, @@ -287,10 +294,11 @@ class SshnpPartialParams { atKeysFilePath: params2.atKeysFilePath ?? params1.atKeysFilePath, identityFile: params2.identityFile ?? params1.identityFile, identityPassphrase: - params2.identityPassphrase ?? params1.identityPassphrase, + params2.identityPassphrase ?? params1.identityPassphrase, sendSshPublicKey: params2.sendSshPublicKey ?? params1.sendSshPublicKey, localSshOptions: params2.localSshOptions ?? params1.localSshOptions, remoteUsername: params2.remoteUsername ?? params1.remoteUsername, + tunnelUsername: params2.tunnelUsername ?? params1.tunnelUsername, verbose: params2.verbose ?? params1.verbose, rootDomain: params2.rootDomain ?? params1.rootDomain, localSshdPort: params2.localSshdPort ?? params1.localSshdPort, @@ -299,7 +307,7 @@ class SshnpPartialParams { remoteSshdPort: params2.remoteSshdPort ?? params1.remoteSshdPort, idleTimeout: params2.idleTimeout ?? params1.idleTimeout, addForwardsToTunnel: - params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, + params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, sshClient: params2.sshClient ?? params1.sshClient, sshAlgorithm: params2.sshAlgorithm ?? params1.sshAlgorithm, ); @@ -339,6 +347,7 @@ class SshnpPartialParams { ? null : List.from(args[SshnpArg.localSshOptionsArg.name]), remoteUsername: args[SshnpArg.remoteUserNameArg.name], + tunnelUsername: args[SshnpArg.tunnelUserNameArg.name], verbose: args[SshnpArg.verboseArg.name], rootDomain: args[SshnpArg.rootDomainArg.name], localSshdPort: args[SshnpArg.localSshdPortArg.name], @@ -353,7 +362,7 @@ class SshnpPartialParams { sshAlgorithm: args[SshnpArg.sshAlgorithmArg.name] == null ? null : SupportedSshAlgorithm.fromString( - args[SshnpArg.sshAlgorithmArg.name]), + args[SshnpArg.sshAlgorithmArg.name]), ); } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_core.dart b/packages/noports_core/lib/src/sshnp/sshnp_core.dart index 71ded03c6..c8271d441 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_core.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_core.dart @@ -46,6 +46,9 @@ abstract class SshnpCore /// The remote username to use for the ssh session String? remoteUsername; + /// The username to use for the initial ssh tunnel session + String? tunnelUsername; + // * Communication Channels /// The channel to communicate with the sshrvd (host) @@ -83,6 +86,9 @@ abstract class SshnpCore /// Set the remote username to use for the ssh session remoteUsername = await sshnpdChannel.resolveRemoteUsername(); + /// Set the username to use for the initial ssh tunnel + tunnelUsername = await sshnpdChannel.resolveTunnelUsername(remoteUsername); + /// Shares the public key if required await sshnpdChannel.sharePublicKeyIfRequired(identityKeyPair); diff --git a/packages/noports_core/lib/src/sshnp/util/sshnp_initial_tunnel_handler/sshnp_dart_initial_tunnel_handler.dart b/packages/noports_core/lib/src/sshnp/util/sshnp_initial_tunnel_handler/sshnp_dart_initial_tunnel_handler.dart index efb6c59f2..ec5fe5eff 100644 --- a/packages/noports_core/lib/src/sshnp/util/sshnp_initial_tunnel_handler/sshnp_dart_initial_tunnel_handler.dart +++ b/packages/noports_core/lib/src/sshnp/util/sshnp_initial_tunnel_handler/sshnp_dart_initial_tunnel_handler.dart @@ -39,17 +39,18 @@ mixin SshnpDartInitialTunnelHandler on SshnpCore throw error; } + var usernameForTunnel = tunnelUsername ?? getUserName(throwIfNull: true)!; try { AtSshKeyPair keyPair = await keyUtil.getKeyPair(identifier: identifier); client = SSHClient( socket, - username: remoteUsername ?? getUserName(throwIfNull: true)!, + username: usernameForTunnel, identities: [keyPair.keyPair], keepAliveInterval: Duration(seconds: 15), ); } catch (e, s) { throw SshnpError( - 'Failed to create SSHClient for ${params.remoteUsername}@${sshrvdChannel.host}:${sshrvdChannel.sshrvdPort} : $e', + 'Failed to create SSHClient for $usernameForTunnel@${sshrvdChannel.host}:${sshrvdChannel.sshrvdPort} : $e', error: e, stackTrace: s, ); @@ -59,7 +60,7 @@ mixin SshnpDartInitialTunnelHandler on SshnpCore await client.authenticated.catchError((e) => throw e); } catch (e, s) { throw SshnpError( - 'Failed to authenticate as ${params.remoteUsername}@${sshrvdChannel.host}:${sshrvdChannel.sshrvdPort} : $e', + 'Failed to authenticate as $usernameForTunnel@${sshrvdChannel.host}:${sshrvdChannel.sshrvdPort} : $e', error: e, stackTrace: s, ); diff --git a/packages/noports_core/lib/src/sshnp/util/sshnp_initial_tunnel_handler/sshnp_openssh_initial_tunnel_handler.dart b/packages/noports_core/lib/src/sshnp/util/sshnp_initial_tunnel_handler/sshnp_openssh_initial_tunnel_handler.dart index 179a4e8e8..f2768f967 100644 --- a/packages/noports_core/lib/src/sshnp/util/sshnp_initial_tunnel_handler/sshnp_openssh_initial_tunnel_handler.dart +++ b/packages/noports_core/lib/src/sshnp/util/sshnp_initial_tunnel_handler/sshnp_openssh_initial_tunnel_handler.dart @@ -16,7 +16,7 @@ mixin SshnpOpensshInitialTunnelHandler on SshnpCore Process? process; // If we are starting an initial tunnel, it should be to sshrvd, // so it is safe to assume that sshrvdChannel is not null here - String argsString = '$remoteUsername@${sshrvdChannel.host}' + String argsString = '$tunnelUsername@${sshrvdChannel.host}' ' -p ${sshrvdChannel.sshrvdPort}' ' -i $identifier' ' -L $localPort:localhost:${params.remoteSshdPort}' diff --git a/packages/noports_core/lib/src/sshnp/util/sshnpd_channel/sshnpd_channel.dart b/packages/noports_core/lib/src/sshnp/util/sshnpd_channel/sshnpd_channel.dart index 058db128f..2dba23ec2 100644 --- a/packages/noports_core/lib/src/sshnp/util/sshnpd_channel/sshnpd_channel.dart +++ b/packages/noports_core/lib/src/sshnp/util/sshnpd_channel/sshnpd_channel.dart @@ -161,6 +161,17 @@ abstract class SshnpdChannel with AsyncInitialization, AtClientBindings { } } + /// Resolve the username to use in the initial ssh tunnel + /// If [params.tunnelUsername] is set, it will be used. + /// Otherwise, the username will be set to [remoteUsername] + Future resolveTunnelUsername(String? remoteUsername) async { + if (params.tunnelUsername != null) { + return params.tunnelUsername!; + } else { + return remoteUsername; + } + } + /// List all available devices from the daemon. /// Returns a [SSHPNPDeviceList] object which contains a map of device names /// and corresponding info, and a list of active devices (devices which also diff --git a/packages/noports_core/test/sshnp/models/sshnp_params_test.dart b/packages/noports_core/test/sshnp/models/sshnp_params_test.dart index 14d2023e4..9a1838334 100644 --- a/packages/noports_core/test/sshnp/models/sshnp_params_test.dart +++ b/packages/noports_core/test/sshnp/models/sshnp_params_test.dart @@ -18,6 +18,7 @@ void main() { expect(params.sendSshPublicKey, isA()); expect(params.localSshOptions, isA>()); expect(params.remoteUsername, isA()); + expect(params.tunnelUsername, isA()); expect(params.verbose, isA()); expect(params.rootDomain, isA()); expect(params.localSshdPort, isA()); @@ -110,6 +111,14 @@ void main() { remoteUsername: 'myUsername'); expect(params.remoteUsername, equals('myUsername')); }); + test('SshnpParams.tunnelUsername test', () { + final params = SshnpParams( + clientAtSign: '', + sshnpdAtSign: '', + host: '', + tunnelUsername: 'myTunnelUsername'); + expect(params.tunnelUsername, equals('myTunnelUsername')); + }); test('SshnpParams.verbose test', () { final params = SshnpParams( clientAtSign: '', sshnpdAtSign: '', host: '', verbose: true); @@ -209,6 +218,7 @@ void main() { params.localSshOptions, equals(DefaultSshnpArgs.localSshOptions)); expect(params.verbose, equals(DefaultArgs.verbose)); expect(params.remoteUsername, isNull); + expect(params.tunnelUsername, isNull); expect(params.atKeysFilePath, isNull); expect(params.rootDomain, equals(DefaultArgs.rootDomain)); expect(params.localSshdPort, equals(DefaultArgs.localSshdPort)); @@ -236,6 +246,7 @@ void main() { sendSshPublicKey: true, localSshOptions: ['-L 127.0.01:8080:127.0.0.1:80'], remoteUsername: 'myUsername', + tunnelUsername: 'myTunnelUsername', verbose: true, rootDomain: 'root.atsign.wtf', localSshdPort: 4567, @@ -260,6 +271,7 @@ void main() { expect( params.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(params.remoteUsername, equals('myUsername')); + expect(params.tunnelUsername, equals('myTunnelUsername')); expect(params.verbose, equals(true)); expect(params.rootDomain, equals('root.atsign.wtf')); expect(params.localSshdPort, equals(4567)); @@ -290,6 +302,7 @@ void main() { params.localSshOptions, equals(DefaultSshnpArgs.localSshOptions)); expect(params.verbose, equals(DefaultArgs.verbose)); expect(params.remoteUsername, isNull); + expect(params.tunnelUsername, isNull); expect(params.atKeysFilePath, isNull); expect(params.rootDomain, equals(DefaultArgs.rootDomain)); expect(params.localSshdPort, equals(DefaultArgs.localSshdPort)); @@ -316,6 +329,7 @@ void main() { '"${SshnpArg.sendSshPublicKeyArg.name}": true,' '"${SshnpArg.localSshOptionsArg.name}": ["-L 127.0.01:8080:127.0.0.1:80"],' '"${SshnpArg.remoteUserNameArg.name}": "myUsername",' + '"${SshnpArg.tunnelUserNameArg.name}": "myTunnelUsername",' '"${SshnpArg.verboseArg.name}": true,' '"${SshnpArg.rootDomainArg.name}": "root.atsign.wtf",' '"${SshnpArg.localSshdPortArg.name}": 4567,' @@ -342,6 +356,7 @@ void main() { expect( params.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(params.remoteUsername, equals('myUsername')); + expect(params.tunnelUsername, equals('myTunnelUsername')); expect(params.verbose, equals(true)); expect(params.rootDomain, equals('root.atsign.wtf')); expect(params.localSshdPort, equals(4567)); @@ -378,6 +393,7 @@ void main() { '${SshnpArg.sendSshPublicKeyArg.bashName} = true', '${SshnpArg.localSshOptionsArg.bashName} = -L 127.0.01:8080:127.0.0.1:80', '${SshnpArg.remoteUserNameArg.bashName} = myUsername', + '${SshnpArg.tunnelUserNameArg.bashName} = myTunnelUsername', '${SshnpArg.verboseArg.bashName} = true', '${SshnpArg.rootDomainArg.bashName} = root.atsign.wtf', '${SshnpArg.localSshdPortArg.bashName} = 4567', @@ -401,6 +417,7 @@ void main() { expect( params.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(params.remoteUsername, equals('myUsername')); + expect(params.tunnelUsername, equals('myTunnelUsername')); expect(params.verbose, equals(true)); expect(params.rootDomain, equals('root.atsign.wtf')); expect(params.localSshdPort, equals(4567)); @@ -422,6 +439,7 @@ void main() { sendSshPublicKey: true, localSshOptions: ['-L 127.0.01:8080:127.0.0.1:80'], remoteUsername: 'myUsername', + tunnelUsername: 'myTunnelUsername', verbose: true, rootDomain: 'root.atsign.wtf', localSshdPort: 4567, @@ -449,6 +467,7 @@ void main() { expect(parsedParams.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(parsedParams.remoteUsername, equals('myUsername')); + expect(parsedParams.tunnelUsername, equals('myTunnelUsername')); expect(parsedParams.verbose, equals(true)); expect(parsedParams.rootDomain, equals('root.atsign.wtf')); expect(parsedParams.localSshdPort, equals(4567)); @@ -467,6 +486,7 @@ void main() { sendSshPublicKey: true, localSshOptions: ['-L 127.0.01:8080:127.0.0.1:80'], remoteUsername: 'myUsername', + tunnelUsername: 'myTunnelUsername', verbose: true, rootDomain: 'root.atsign.wtf', localSshdPort: 4567, @@ -492,6 +512,7 @@ void main() { expect(argMap[SshnpArg.localSshOptionsArg.name], equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(argMap[SshnpArg.remoteUserNameArg.name], equals('myUsername')); + expect(argMap[SshnpArg.tunnelUserNameArg.name], equals('myTunnelUsername')); expect(argMap[SshnpArg.verboseArg.name], equals(true)); expect(argMap[SshnpArg.rootDomainArg.name], equals('root.atsign.wtf')); expect(argMap[SshnpArg.localSshdPortArg.name], equals(4567)); @@ -518,6 +539,7 @@ void main() { sendSshPublicKey: true, localSshOptions: ['-L 127.0.01:8080:127.0.0.1:80'], remoteUsername: 'myUsername', + tunnelUsername: 'myTunnelUsername', verbose: true, rootDomain: 'root.atsign.wtf', localSshdPort: 4567, @@ -542,6 +564,7 @@ void main() { expect(parsedParams.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(parsedParams.remoteUsername, equals('myUsername')); + expect(parsedParams.tunnelUsername, equals('myTunnelUsername')); expect(parsedParams.verbose, equals(true)); expect(parsedParams.rootDomain, equals('root.atsign.wtf')); expect(parsedParams.localSshdPort, equals(4567)); @@ -571,6 +594,7 @@ void main() { expect(partialParams.sendSshPublicKey, isA()); expect(partialParams.localSshOptions, isA?>()); expect(partialParams.remoteUsername, isA()); + expect(partialParams.tunnelUsername, isA()); expect(partialParams.verbose, isA()); expect(partialParams.rootDomain, isA()); expect(partialParams.localSshdPort, isA()); @@ -632,6 +656,10 @@ void main() { final params = SshnpPartialParams(remoteUsername: 'myUsername'); expect(params.remoteUsername, equals('myUsername')); }); + test('SshnpPartialParams.tunnelUsername test', () { + final params = SshnpPartialParams(tunnelUsername: 'myTunnelUsername'); + expect(params.tunnelUsername, equals('myTunnelUsername')); + }); test('SshnpPartialParams.verbose test', () { final params = SshnpPartialParams(verbose: true); expect(params.verbose, equals(true)); @@ -700,6 +728,7 @@ void main() { expect(params.localSshOptions, isNull); expect(params.verbose, isNull); expect(params.remoteUsername, isNull); + expect(params.tunnelUsername, isNull); expect(params.rootDomain, isNull); expect(params.localSshdPort, isNull); expect(params.legacyDaemon, isNull); @@ -726,6 +755,7 @@ void main() { sendSshPublicKey: true, localSshOptions: ['-L 127.0.01:8080:127.0.0.1:80'], remoteUsername: 'myUsername', + tunnelUsername: 'myTunnelUsername', verbose: true, rootDomain: 'root.atsign.wtf', localSshdPort: 4567, @@ -749,6 +779,7 @@ void main() { expect( params.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(params.remoteUsername, equals('myUsername')); + expect(params.tunnelUsername, equals('myTunnelUsername')); expect(params.verbose, equals(true)); expect(params.rootDomain, equals('root.atsign.wtf')); expect(params.localSshdPort, equals(4567)); @@ -774,6 +805,7 @@ void main() { sendSshPublicKey: true, localSshOptions: ['-L 127.0.01:8080:127.0.0.1:80'], remoteUsername: 'myUsername', + tunnelUsername: 'myTunnelUsername', verbose: true, rootDomain: 'root.atsign.wtf', localSshdPort: 4567, @@ -798,6 +830,7 @@ void main() { expect( params.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(params.remoteUsername, equals('myUsername')); + expect(params.tunnelUsername, equals('myTunnelUsername')); expect(params.verbose, equals(true)); expect(params.rootDomain, equals('root.atsign.wtf')); expect(params.localSshdPort, equals(4567)); @@ -823,6 +856,7 @@ void main() { sendSshPublicKey: true, localSshOptions: ['-L 127.0.01:8080:127.0.0.1:80'], remoteUsername: 'myUsername', + tunnelUsername: 'myTunnelUsername', verbose: true, rootDomain: 'root.atsign.wtf', localSshdPort: 4567, @@ -850,6 +884,7 @@ void main() { expect(parsedParams.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(parsedParams.remoteUsername, equals('myUsername')); + expect(parsedParams.tunnelUsername, equals('myTunnelUsername')); expect(parsedParams.verbose, equals(true)); expect(parsedParams.rootDomain, equals('root.atsign.wtf')); expect(parsedParams.localSshdPort, equals(4567)); @@ -869,6 +904,7 @@ void main() { '"${SshnpArg.sendSshPublicKeyArg.name}": true,' '"${SshnpArg.localSshOptionsArg.name}": ["-L 127.0.01:8080:127.0.0.1:80"],' '"${SshnpArg.remoteUserNameArg.name}": "myUsername",' + '"${SshnpArg.tunnelUserNameArg.name}": "myTunnelUsername",' '"${SshnpArg.verboseArg.name}": true,' '"${SshnpArg.rootDomainArg.name}": "root.atsign.wtf",' '"${SshnpArg.localSshdPortArg.name}": 4567,' @@ -895,6 +931,7 @@ void main() { expect( params.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(params.remoteUsername, equals('myUsername')); + expect(params.tunnelUsername, equals('myTunnelUsername')); expect(params.verbose, equals(true)); expect(params.rootDomain, equals('root.atsign.wtf')); expect(params.localSshdPort, equals(4567)); @@ -921,6 +958,7 @@ void main() { SshnpArg.sendSshPublicKeyArg.name: true, SshnpArg.localSshOptionsArg.name: ['-L 127.0.01:8080:127.0.0.1:80'], SshnpArg.remoteUserNameArg.name: 'myUsername', + SshnpArg.tunnelUserNameArg.name: 'myTunnelUsername', SshnpArg.verboseArg.name: true, SshnpArg.rootDomainArg.name: 'root.atsign.wtf', SshnpArg.localSshdPortArg.name: 4567, @@ -945,6 +983,7 @@ void main() { expect( params.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(params.remoteUsername, equals('myUsername')); + expect(params.tunnelUsername, equals('myTunnelUsername')); expect(params.verbose, equals(true)); expect(params.rootDomain, equals('root.atsign.wtf')); expect(params.localSshdPort, equals(4567)); @@ -983,6 +1022,8 @@ void main() { '-L 127.0.01:8080:127.0.0.1:80', '--${SshnpArg.remoteUserNameArg.name}', 'myUsername', + '--${SshnpArg.tunnelUserNameArg.name}', + 'myTunnelUsername', '--${SshnpArg.verboseArg.name}', 'true', '--${SshnpArg.rootDomainArg.name}', @@ -1018,6 +1059,7 @@ void main() { expect( params.localSshOptions, equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(params.remoteUsername, equals('myUsername')); + expect(params.tunnelUsername, equals('myTunnelUsername')); expect(params.verbose, equals(true)); expect(params.rootDomain, equals('root.atsign.wtf')); expect(params.localSshdPort, equals(4567)); diff --git a/packages/noports_core/test/sshnp/sshnp_core_test.dart b/packages/noports_core/test/sshnp/sshnp_core_test.dart index ca290106e..467f1d8f5 100644 --- a/packages/noports_core/test/sshnp/sshnp_core_test.dart +++ b/packages/noports_core/test/sshnp/sshnp_core_test.dart @@ -54,6 +54,8 @@ void main() { .thenAnswer((_) async {}); when(() => mockSshnpdChannel.resolveRemoteUsername()) .thenAnswer((_) async => 'myRemoteUsername'); + when(() => mockSshnpdChannel.resolveTunnelUsername(any())) + .thenAnswer((_) async => 'myTunnelUsername'); when(() => mockSshnpdChannel.sharePublicKeyIfRequired( identityKeyPair ?? any())).thenAnswer((_) async {}); when(() => mockSshrvdChannel.callInitialization()) diff --git a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart index e94eeb352..d76a6ea4e 100644 --- a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart +++ b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart @@ -40,6 +40,8 @@ void main() { .thenAnswer((_) async {}); when(() => mockSshnpdChannel.resolveRemoteUsername()) .thenAnswer((_) async => 'myRemoteUsername'); + when(() => mockSshnpdChannel.resolveTunnelUsername(any())) + .thenAnswer((_) async => 'myTunnelUsername'); when(() => mockSshnpdChannel.sharePublicKeyIfRequired(identityKeyPair)) .thenAnswer((_) async {}); when(() => mockSshrvdChannel.callInitialization()) diff --git a/packages/sshnp_gui/lib/l10n/app_en.arb b/packages/sshnp_gui/lib/l10n/app_en.arb index 85999dda4..cb951e9c6 100644 --- a/packages/sshnp_gui/lib/l10n/app_en.arb +++ b/packages/sshnp_gui/lib/l10n/app_en.arb @@ -72,6 +72,7 @@ "success": "Success", "switchAtsign" : "Switch atSign", "to" : "To", + "tunnelUserName" : "Remote Username to use for initial ssh tunnel", "username" : "Username", "usernameHint": "The user name on this host", "verbose" : "Verbose Logging", From 63c05d72284e1106abc3717336580070c790c5c6 Mon Sep 17 00:00:00 2001 From: gkc Date: Wed, 22 Nov 2023 15:04:40 +0000 Subject: [PATCH 2/2] refactor: made remoteUsername a required named parameter in SshnpdChannel.resolveTunnelUsername style: ran dart format test: added unit tests of SshnpdChannel for tunnelUsername supplied and tunnelUsername not supplied --- .../lib/src/sshnp/models/sshnp_params.dart | 23 +++++----- .../lib/src/sshnp/sshnp_core.dart | 3 +- .../util/sshnpd_channel/sshnpd_channel.dart | 3 +- packages/noports_core/lib/sshrv.dart | 2 +- .../models/config_file_repository_test.dart | 19 ++++++--- .../test/sshnp/models/sshnp_params_test.dart | 3 +- .../test/sshnp/sshnp_core_test.dart | 42 ++++++++++++++++++- .../noports_core/test/sshnp/sshnp_mocks.dart | 1 - .../sshnp_local_ssh_key_handler_test.dart | 3 +- 9 files changed, 76 insertions(+), 23 deletions(-) diff --git a/packages/noports_core/lib/src/sshnp/models/sshnp_params.dart b/packages/noports_core/lib/src/sshnp/models/sshnp_params.dart index be37b9945..cfe3f81f8 100644 --- a/packages/noports_core/lib/src/sshnp/models/sshnp_params.dart +++ b/packages/noports_core/lib/src/sshnp/models/sshnp_params.dart @@ -38,8 +38,9 @@ class SshnpParams { final SupportedSshAlgorithm sshAlgorithm; /// Special Arguments - final String? - profileName; // automatically populated with the filename if from a configFile + + /// automatically populated with the filename if from a configFile + final String? profileName; /// Operation flags final bool listDevices; @@ -96,7 +97,7 @@ class SshnpParams { atKeysFilePath: params2.atKeysFilePath ?? params1.atKeysFilePath, identityFile: params2.identityFile ?? params1.identityFile, identityPassphrase: - params2.identityPassphrase ?? params1.identityPassphrase, + params2.identityPassphrase ?? params1.identityPassphrase, sendSshPublicKey: params2.sendSshPublicKey ?? params1.sendSshPublicKey, localSshOptions: params2.localSshOptions ?? params1.localSshOptions, remoteUsername: params2.remoteUsername ?? params1.remoteUsername, @@ -109,7 +110,7 @@ class SshnpParams { remoteSshdPort: params2.remoteSshdPort ?? params1.remoteSshdPort, idleTimeout: params2.idleTimeout ?? params1.idleTimeout, addForwardsToTunnel: - params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, + params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, sshClient: params2.sshClient ?? params1.sshClient, sshAlgorithm: params2.sshAlgorithm ?? params1.sshAlgorithm, ); @@ -137,9 +138,9 @@ class SshnpParams { identityFile: partial.identityFile, identityPassphrase: partial.identityPassphrase, sendSshPublicKey: - partial.sendSshPublicKey ?? DefaultSshnpArgs.sendSshPublicKey, + partial.sendSshPublicKey ?? DefaultSshnpArgs.sendSshPublicKey, localSshOptions: - partial.localSshOptions ?? DefaultSshnpArgs.localSshOptions, + partial.localSshOptions ?? DefaultSshnpArgs.localSshOptions, verbose: partial.verbose ?? DefaultArgs.verbose, remoteUsername: partial.remoteUsername, tunnelUsername: partial.tunnelUsername, @@ -151,7 +152,7 @@ class SshnpParams { remoteSshdPort: partial.remoteSshdPort ?? DefaultArgs.remoteSshdPort, idleTimeout: partial.idleTimeout ?? DefaultArgs.idleTimeout, addForwardsToTunnel: - partial.addForwardsToTunnel ?? DefaultArgs.addForwardsToTunnel, + partial.addForwardsToTunnel ?? DefaultArgs.addForwardsToTunnel, sshClient: partial.sshClient ?? DefaultSshnpArgs.sshClient, sshAlgorithm: partial.sshAlgorithm ?? DefaultArgs.sshAlgorithm, ); @@ -205,7 +206,7 @@ class SshnpParams { SshnpArg.sshAlgorithmArg.name: sshAlgorithm.toString(), }; args.removeWhere( - (key, value) => !parserType.shouldParse(SshnpArg.fromName(key).parseWhen), + (key, value) => !parserType.shouldParse(SshnpArg.fromName(key).parseWhen), ); return args; } @@ -294,7 +295,7 @@ class SshnpPartialParams { atKeysFilePath: params2.atKeysFilePath ?? params1.atKeysFilePath, identityFile: params2.identityFile ?? params1.identityFile, identityPassphrase: - params2.identityPassphrase ?? params1.identityPassphrase, + params2.identityPassphrase ?? params1.identityPassphrase, sendSshPublicKey: params2.sendSshPublicKey ?? params1.sendSshPublicKey, localSshOptions: params2.localSshOptions ?? params1.localSshOptions, remoteUsername: params2.remoteUsername ?? params1.remoteUsername, @@ -307,7 +308,7 @@ class SshnpPartialParams { remoteSshdPort: params2.remoteSshdPort ?? params1.remoteSshdPort, idleTimeout: params2.idleTimeout ?? params1.idleTimeout, addForwardsToTunnel: - params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, + params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, sshClient: params2.sshClient ?? params1.sshClient, sshAlgorithm: params2.sshAlgorithm ?? params1.sshAlgorithm, ); @@ -362,7 +363,7 @@ class SshnpPartialParams { sshAlgorithm: args[SshnpArg.sshAlgorithmArg.name] == null ? null : SupportedSshAlgorithm.fromString( - args[SshnpArg.sshAlgorithmArg.name]), + args[SshnpArg.sshAlgorithmArg.name]), ); } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_core.dart b/packages/noports_core/lib/src/sshnp/sshnp_core.dart index c8271d441..0de4d3f25 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_core.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_core.dart @@ -87,7 +87,8 @@ abstract class SshnpCore remoteUsername = await sshnpdChannel.resolveRemoteUsername(); /// Set the username to use for the initial ssh tunnel - tunnelUsername = await sshnpdChannel.resolveTunnelUsername(remoteUsername); + tunnelUsername = await sshnpdChannel.resolveTunnelUsername( + remoteUsername: remoteUsername); /// Shares the public key if required await sshnpdChannel.sharePublicKeyIfRequired(identityKeyPair); diff --git a/packages/noports_core/lib/src/sshnp/util/sshnpd_channel/sshnpd_channel.dart b/packages/noports_core/lib/src/sshnp/util/sshnpd_channel/sshnpd_channel.dart index 2dba23ec2..651528cf4 100644 --- a/packages/noports_core/lib/src/sshnp/util/sshnpd_channel/sshnpd_channel.dart +++ b/packages/noports_core/lib/src/sshnp/util/sshnpd_channel/sshnpd_channel.dart @@ -164,7 +164,8 @@ abstract class SshnpdChannel with AsyncInitialization, AtClientBindings { /// Resolve the username to use in the initial ssh tunnel /// If [params.tunnelUsername] is set, it will be used. /// Otherwise, the username will be set to [remoteUsername] - Future resolveTunnelUsername(String? remoteUsername) async { + Future resolveTunnelUsername( + {required String? remoteUsername}) async { if (params.tunnelUsername != null) { return params.tunnelUsername!; } else { diff --git a/packages/noports_core/lib/sshrv.dart b/packages/noports_core/lib/sshrv.dart index 3a2b323b9..3cfa5b3a9 100644 --- a/packages/noports_core/lib/sshrv.dart +++ b/packages/noports_core/lib/sshrv.dart @@ -1,3 +1,3 @@ library noports_core_sshrv; -export 'src/sshrv/sshrv.dart'; \ No newline at end of file +export 'src/sshrv/sshrv.dart'; diff --git a/packages/noports_core/test/sshnp/models/config_file_repository_test.dart b/packages/noports_core/test/sshnp/models/config_file_repository_test.dart index 29a6bb237..92d5b8ac6 100644 --- a/packages/noports_core/test/sshnp/models/config_file_repository_test.dart +++ b/packages/noports_core/test/sshnp/models/config_file_repository_test.dart @@ -8,14 +8,23 @@ void main() { test('ConfigFileRepository.atKeyFromProfileName test', () async { String profileName = 'myProfileName'; - expect(ConfigFileRepository.getDefaultSshnpConfigDirectory(getHomeDirectory()!), isA()); - expect(ConfigFileRepository.fromProfileName(profileName), isA>()); + expect( + ConfigFileRepository.getDefaultSshnpConfigDirectory( + getHomeDirectory()!), + isA()); + expect(ConfigFileRepository.fromProfileName(profileName), + isA>()); expect(ConfigFileRepository.fromProfileName(profileName), completes); expect( - await ConfigFileRepository.fromProfileName(profileName, basenameOnly: false), - equals(path.join(getHomeDirectory()!, '.sshnp', 'config', '$profileName.env')), + await ConfigFileRepository.fromProfileName(profileName, + basenameOnly: false), + equals(path.join( + getHomeDirectory()!, '.sshnp', 'config', '$profileName.env')), ); - expect(await ConfigFileRepository.fromProfileName(profileName, basenameOnly: true), equals('$profileName.env')); + expect( + await ConfigFileRepository.fromProfileName(profileName, + basenameOnly: true), + equals('$profileName.env')); }); group('[depends on ConfigFileRepository.atKeyFromProfileName]', () { diff --git a/packages/noports_core/test/sshnp/models/sshnp_params_test.dart b/packages/noports_core/test/sshnp/models/sshnp_params_test.dart index 9a1838334..6b43debf2 100644 --- a/packages/noports_core/test/sshnp/models/sshnp_params_test.dart +++ b/packages/noports_core/test/sshnp/models/sshnp_params_test.dart @@ -512,7 +512,8 @@ void main() { expect(argMap[SshnpArg.localSshOptionsArg.name], equals(['-L 127.0.01:8080:127.0.0.1:80'])); expect(argMap[SshnpArg.remoteUserNameArg.name], equals('myUsername')); - expect(argMap[SshnpArg.tunnelUserNameArg.name], equals('myTunnelUsername')); + expect(argMap[SshnpArg.tunnelUserNameArg.name], + equals('myTunnelUsername')); expect(argMap[SshnpArg.verboseArg.name], equals(true)); expect(argMap[SshnpArg.rootDomainArg.name], equals('root.atsign.wtf')); expect(argMap[SshnpArg.localSshdPortArg.name], equals(4567)); diff --git a/packages/noports_core/test/sshnp/sshnp_core_test.dart b/packages/noports_core/test/sshnp/sshnp_core_test.dart index 467f1d8f5..772052231 100644 --- a/packages/noports_core/test/sshnp/sshnp_core_test.dart +++ b/packages/noports_core/test/sshnp/sshnp_core_test.dart @@ -54,7 +54,8 @@ void main() { .thenAnswer((_) async {}); when(() => mockSshnpdChannel.resolveRemoteUsername()) .thenAnswer((_) async => 'myRemoteUsername'); - when(() => mockSshnpdChannel.resolveTunnelUsername(any())) + when(() => mockSshnpdChannel.resolveTunnelUsername( + remoteUsername: any(named: 'remoteUsername'))) .thenAnswer((_) async => 'myTunnelUsername'); when(() => mockSshnpdChannel.sharePublicKeyIfRequired( identityKeyPair ?? any())).thenAnswer((_) async {}); @@ -131,6 +132,8 @@ void main() { () => stubbedInitialize(), () => mockSshnpdChannel.callInitialization(), () => mockSshnpdChannel.resolveRemoteUsername(), + () => mockSshnpdChannel.resolveTunnelUsername( + remoteUsername: 'myRemoteUsername'), () => mockSshnpdChannel .sharePublicKeyIfRequired(sshnpCore.identityKeyPair), () => mockSshrvdChannel.callInitialization(), @@ -142,6 +145,8 @@ void main() { verifyNever(() => stubbedInitialize()); verifyNever(() => mockSshnpdChannel.callInitialization()); verifyNever(() => mockSshnpdChannel.resolveRemoteUsername()); + verifyNever(() => mockSshnpdChannel.resolveTunnelUsername( + remoteUsername: 'myRemoteUsername')); verifyNever(() => mockSshnpdChannel .sharePublicKeyIfRequired(sshnpCore.identityKeyPair)); verifyNever(() => mockSshrvdChannel.callInitialization()); @@ -155,6 +160,41 @@ void main() { verifyNever(() => stubbedCompleteInitialization()); verifyNever(() => mockSshrvdChannel.callInitialization()); }); + test('tunnelUsername not supplied', () async { + final params = SshnpParams( + clientAtSign: '@client', + sshnpdAtSign: '@daemon', + host: 'foo.bar.test', + remoteUsername: 'alice'); + final channel = SshnpdDefaultChannel( + atClient: mockAtClient, + params: params, + sessionId: 'test_tunnelUsername_not_supplied', + namespace: 'test'); + final remoteUsername = await channel.resolveRemoteUsername(); + expect(remoteUsername, 'alice'); + expect( + await channel.resolveTunnelUsername(remoteUsername: remoteUsername), + 'alice'); + }); + test('tunnelUsername supplied', () async { + final params = SshnpParams( + clientAtSign: '@client', + sshnpdAtSign: '@daemon', + host: 'foo.bar.test', + remoteUsername: 'alice', + tunnelUsername: 'bob'); + final channel = SshnpdDefaultChannel( + atClient: mockAtClient, + params: params, + sessionId: 'test_tunnelUsername_supplied', + namespace: 'test'); + final remoteUsername = await channel.resolveRemoteUsername(); + expect(remoteUsername, 'alice'); + expect( + await channel.resolveTunnelUsername(remoteUsername: remoteUsername), + 'bob'); + }); }); // group Initialization }); // group SshnpCore } diff --git a/packages/noports_core/test/sshnp/sshnp_mocks.dart b/packages/noports_core/test/sshnp/sshnp_mocks.dart index 27667e4fc..fb54234dc 100644 --- a/packages/noports_core/test/sshnp/sshnp_mocks.dart +++ b/packages/noports_core/test/sshnp/sshnp_mocks.dart @@ -45,4 +45,3 @@ abstract class StartProcessCaller { } class StartProcessStub extends Mock implements StartProcessCaller {} - diff --git a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart index d76a6ea4e..95df64a6a 100644 --- a/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart +++ b/packages/noports_core/test/sshnp/util/sshnp_ssh_key_handler/sshnp_local_ssh_key_handler_test.dart @@ -40,7 +40,8 @@ void main() { .thenAnswer((_) async {}); when(() => mockSshnpdChannel.resolveRemoteUsername()) .thenAnswer((_) async => 'myRemoteUsername'); - when(() => mockSshnpdChannel.resolveTunnelUsername(any())) + when(() => mockSshnpdChannel.resolveTunnelUsername( + remoteUsername: any(named: 'remoteUsername'))) .thenAnswer((_) async => 'myTunnelUsername'); when(() => mockSshnpdChannel.sharePublicKeyIfRequired(identityKeyPair)) .thenAnswer((_) async {});