Compare commits

...

80 Commits

Author SHA1 Message Date
Faryan Rezagholi
8a5f0f248e open credential manager from connection tree window 2021-09-04 00:52:04 +02:00
Faryan Rezagholi
c1931ff4cd hide password/username/domain fields in connection properties 2021-09-04 00:27:42 +02:00
Faryan Rezagholi
12cb7ad7b0 merged from develop 2021-09-01 01:26:21 +02:00
David Sparer
4349b1b65b dont save settings on individual settings pages 2019-05-19 16:23:00 -05:00
David Sparer
bb04605dc3 Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteNGTests/Connection/DefaultConnectionInheritanceTests.cs
#	mRemoteNGTests/TestHelpers/ConnectionInfoHelpers.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsDeserializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsSerializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableSerializer.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer27.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs
#	mRemoteV1/Connection/AbstractConnectionRecord.cs
#	mRemoteV1/Connection/ConnectionInitiator.cs
#	mRemoteV1/Connection/Protocol/RDP/RdpProtocol.cs
#	mRemoteV1/Resources/Help/ui_external_tools.htm
#	mRemoteV1/Resources/Language/Language.resx
#	mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs
#	mRemoteV1/UI/Forms/OptionsPages/CredentialsPage.Designer.cs
#	mRemoteV1/UI/Window/ConfigWindow.cs
#	mRemoteV1/UI/Window/ConnectionTreeWindow.Designer.cs
#	mRemoteV1/mRemoteV1.csproj
2019-05-19 16:19:18 -05:00
David Sparer
294bd2f7a4 fixed event invocator warnings 2019-03-19 20:25:59 -05:00
David Sparer
a3aa323cce updated html documentation for #680 2019-03-19 20:15:16 -05:00
David Sparer
355cd63acb Merge branch 'develop' into reapply_credential_manager 2019-03-19 18:01:37 -05:00
David Sparer
93d50b0818 Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteNGTests/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManager27DeserializerTests.cs
#	mRemoteNGTests/packages.config
#	mRemoteV1/Config/Connections/SqlConnectionsSaver.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsSerializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer27.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs
#	mRemoteV1/Connection/AbstractConnectionRecord.cs
#	mRemoteV1/Connection/ConnectionInfoInheritance.cs
#	mRemoteV1/Connection/ConnectionsService.cs
#	mRemoteV1/Connection/Protocol/Http/Connection.Protocol.HTTPBase.cs
#	mRemoteV1/Properties/Settings.settings
#	mRemoteV1/Resources/Language/Language.de.resx
#	mRemoteV1/Resources/Language/Language.resx
#	mRemoteV1/Resources/Language/Language.ru.resx
#	mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs
#	mRemoteV1/mRemoteV1.csproj
2019-03-09 13:49:23 -06:00
Faryan Rezagholi
8779776ad5 removed minimize button from credentials manager 2019-02-20 19:45:18 +01:00
Faryan Rezagholi
3bb285d180 removed unused form 2019-02-20 19:42:30 +01:00
Faryan Rezagholi
052796c794 adjusted text box positioning 2019-02-20 19:08:25 +01:00
Faryan Rezagholi
33a6dbb4c3 made more hardcoded strings translateable 2019-02-20 18:32:35 +01:00
Faryan Rezagholi
953ddaa292 merged develop 2019-02-19 22:39:58 +01:00
Faryan Rezagholi
c065b86dbd revised credential manager forms design 2019-02-19 22:38:12 +01:00
David Sparer
95859b96ff Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/App/Export.cs
#	mRemoteV1/App/Initialization/CredsAndConsSetup.cs
#	mRemoteV1/App/Runtime.cs
#	mRemoteV1/Config/Connections/ConnectionsLoadedEventArgs.cs
#	mRemoteV1/Config/Connections/ConnectionsSavedEventArgs.cs
#	mRemoteV1/Config/Connections/CsvConnectionsSaver.cs
#	mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs
#	mRemoteV1/Config/CredentialHarvester.cs
#	mRemoteV1/Config/CredentialRepositoryListLoader.cs
#	mRemoteV1/Config/CredentialRepositoryListSaver.cs
#	mRemoteV1/Config/DataProviders/FileDataProvider.cs
#	mRemoteV1/Config/Import/MRemoteNGCsvImporter.cs
#	mRemoteV1/Config/Import/MRemoteNGXmlImporter.cs
#	mRemoteV1/Config/Import/RemoteDesktopConnectionImporter.cs
#	mRemoteV1/Config/Putty/PuttySessionsRegistryProvider.cs
#	mRemoteV1/Config/Putty/PuttySessionsXmingProvider.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsDeserializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsSerializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableDeserializer.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableSerializer.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer27.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsSerializer.cs
#	mRemoteV1/Config/Serializers/CredentialProviderSerializer/CredentialRepositoryListDeserializer.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/PuttyConnectionManagerDeserializer.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionDeserializer.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs
#	mRemoteV1/Connection/AbstractConnectionRecord.cs
#	mRemoteV1/Connection/ConnectionInfo.cs
#	mRemoteV1/Connection/ConnectionInfoInheritance.cs
#	mRemoteV1/Connection/ConnectionInitiator.cs
#	mRemoteV1/Connection/ConnectionsService.cs
#	mRemoteV1/Connection/DefaultConnectionInfo.cs
#	mRemoteV1/Connection/Protocol/Http/Connection.Protocol.HTTPBase.cs
#	mRemoteV1/Connection/Protocol/ICA/IcaProtocol.cs
#	mRemoteV1/Connection/Protocol/IntegratedProgram.cs
#	mRemoteV1/Connection/Protocol/ProtocolBase.cs
#	mRemoteV1/Connection/Protocol/PuttyBase.cs
#	mRemoteV1/Connection/Protocol/RDP/RdpProtocol.cs
#	mRemoteV1/Connection/Protocol/VNC/Connection.Protocol.VNC.cs
#	mRemoteV1/Connection/PuttySessionInfo.cs
#	mRemoteV1/Credential/CredentialRecordTypeConverter.cs
#	mRemoteV1/Credential/CredentialServiceFacade.cs
#	mRemoteV1/Credential/CredentialServiceFactory.cs
#	mRemoteV1/Credential/Records/UnavailableCredentialRecord.cs
#	mRemoteV1/Credential/Repositories/XmlCredentialRepository.cs
#	mRemoteV1/Credential/Repositories/XmlCredentialRepositoryFactory.cs
#	mRemoteV1/Properties/Settings.settings
#	mRemoteV1/Schemas/mremoteng_confcons_v2_7.xsd
#	mRemoteV1/Tools/CustomCollections/FullyObservableCollection.cs
#	mRemoteV1/Tools/Extensions.cs
#	mRemoteV1/Tools/ExternalTool.cs
#	mRemoteV1/Tools/ExternalToolArgumentParser.cs
#	mRemoteV1/UI/Controls/Base/NGComboBox.cs
#	mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs
#	mRemoteV1/UI/Controls/CredentialRecordListBox.cs
#	mRemoteV1/UI/Controls/QuickConnectToolStrip.cs
#	mRemoteV1/UI/Forms/OptionsPages/CredentialsPage.cs
#	mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs
#	mRemoteV1/UI/Forms/frmMain.cs
#	mRemoteV1/UI/Forms/frmOptions.cs
#	mRemoteV1/UI/Menu/ToolsMenu.cs
#	mRemoteV1/UI/Window/ConfigWindow.cs
#	mRemoteV1/UI/Window/ConnectionTreeWindow.Designer.cs
#	mRemoteV1/UI/Window/ConnectionTreeWindow.cs
#	mRemoteV1/UI/Window/ConnectionWindow.cs
#	mRemoteV1/app.config
2019-02-17 11:21:22 -06:00
David Sparer
ba62c17ea2 added some interface documentation 2019-01-27 18:01:34 -06:00
David Sparer
1f296f2f72 slight fix to the optional<T> tests 2019-01-27 16:59:02 -06:00
David Sparer
89bb4d45b3 minor tweaks to the import form 2019-01-27 16:58:39 -06:00
David Sparer
6e417ed47e ensure specified creds can be parsed even if no connectioninfo is selected 2019-01-27 12:33:14 -06:00
David Sparer
f3a7d97016 allow specifying specific credential record in external tool arguments
related to #680
2019-01-27 11:31:41 -06:00
David Sparer
530a4e165d extracted interface from credential service to make testing easier 2019-01-27 09:58:28 -06:00
David Sparer
9e217dba79 began creating cred import form 2019-01-23 22:16:08 -06:00
David Sparer
d524df6315 minor efficiency gain 2019-01-22 13:04:58 -06:00
David Sparer
5e224f5b5b ensure assigned cred id property cannot be null 2019-01-22 12:51:41 -06:00
David Sparer
3649927618 fixed bug with connection tree not appearing 2019-01-22 12:48:12 -06:00
David Sparer
4b736176bf request iconnectiontree interface in more places 2019-01-22 06:01:33 -06:00
David Sparer
f78ca1e9fd some more deserialization refactoring 2019-01-21 17:52:18 -06:00
David Sparer
739112a3ff added tests for SaveConnectionsOnEdit
had to create some new interfaces and refactor a bit to make it testable
2019-01-20 15:45:09 -06:00
David Sparer
923b9efd40 began converting the database serializers to work with cred manager 2019-01-20 14:51:09 -06:00
David Sparer
f0d0b5ae21 reduced duplication in rdcm tests 2019-01-20 13:46:34 -06:00
David Sparer
8906e08704 made the rdcm 2.7 importer compatible with credential management 2019-01-20 12:38:39 -06:00
David Sparer
9ead7e8e16 fixed an issue with putty session importing
cred id was not being set. also added a test for it
2019-01-20 11:18:40 -06:00
David Sparer
788ca79ece fixed rdp file importer to support cred manager 2019-01-20 11:15:33 -06:00
David Sparer
88558a353c fixed putty connection manager credential serialization 2019-01-19 14:22:04 -06:00
David Sparer
7fd9abbbc8 resolved csv deserializer test 2019-01-19 13:19:06 -06:00
David Sparer
b029b35df7 Merge remote-tracking branch 'origin/develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/Properties/Settings.Designer.cs
#	mRemoteV1/Properties/Settings.settings
#	mRemoteV1/app.config
2019-01-19 12:51:43 -06:00
David Sparer
187ca5e55b started to redo how cred harvesting works in order to apply it to all importers/deserializers 2019-01-14 16:32:51 -06:00
David Sparer
53c26d8a91 updated tests and references to using cred records rather than user/domain/pass of the connection 2019-01-14 14:02:41 -06:00
David Sparer
159f25b8a1 fixed exttools arg parser tests 2019-01-14 09:54:19 -06:00
David Sparer
f4904f350e minor cleanup 2019-01-14 07:59:09 -06:00
David Sparer
ea59f6ad9d centralized credential lookup logic 2019-01-14 07:56:31 -06:00
David Sparer
4bba05f737 minor cleanup of cred harvester, slightly more efficient 2019-01-14 07:35:37 -06:00
David Sparer
569cf38f24 fixed a cred harvester test 2019-01-14 07:35:13 -06:00
David Sparer
6f8cde4d8e added a method comment 2019-01-13 14:45:07 -06:00
David Sparer
753ea9b421 connections now correctly use default cred if no credential is supplied 2019-01-13 14:42:24 -06:00
David Sparer
a5ea867b6d began hooking up the default credential to the manager 2019-01-13 12:14:28 -06:00
David Sparer
80228966f9 double clicking cred repo tree item opens editor page 2019-01-13 11:27:42 -06:00
David Sparer
b8298ecb14 minor display changes of the repo manager 2019-01-13 11:19:03 -06:00
David Sparer
ee1db6ff8b refactored some methods/fields to be more descriptive 2019-01-13 11:07:58 -06:00
David Sparer
aab1fb1dd2 minor cleanup 2019-01-13 11:05:45 -06:00
David Sparer
fbea9d1ede hooked up remaining cred repo management buttons 2019-01-13 10:57:33 -06:00
David Sparer
75cfc9c75c Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/Properties/Settings.Designer.cs
#	mRemoteV1/Properties/Settings.settings
#	mRemoteV1/UI/Forms/frmMain.cs
#	mRemoteV1/app.config
2019-01-13 08:52:51 -06:00
David Sparer
f60a481902 Merge branch 'develop' into reapply_credential_manager 2019-01-09 10:27:26 -06:00
David Sparer
e89c77a1bc fixed mis-merge 2019-01-09 10:26:40 -06:00
David Sparer
45766b8c12 Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/UI/Forms/OptionsPages/CredentialsPage.Designer.cs
2019-01-09 07:32:09 -06:00
David Sparer
1c44fcb070 Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs
#	mRemoteV1/UI/Controls/Base/NGComboBox.cs
#	mRemoteV1/UI/Controls/NewPasswordWithVerification.Designer.cs
#	mRemoteV1/mRemoteV1.csproj
2019-01-05 07:48:43 -06:00
David Sparer
25aa815a82 began reworking the repo list management screen 2018-12-31 14:17:31 -06:00
David Sparer
a2542f1b18 rearranged some cred manager UI files 2018-12-31 11:13:48 -06:00
David Sparer
2e0979dc5a simplified screens for creating and editing creds 2018-12-31 11:05:36 -06:00
David Sparer
e2ebf25b7b Merge branch 'develop' into reapply_credential_manager 2018-12-31 09:32:47 -06:00
David Sparer
cc44b830a2 scaled images in the unlocker form 2018-12-30 15:28:47 -06:00
David Sparer
687cb6c7bc cred unlocker wont appear if there are no repos to unlock 2018-12-30 14:42:37 -06:00
David Sparer
15f028157e inserted a new page in the upgrade process to view harvested credentials 2018-12-30 14:33:42 -06:00
David Sparer
63ebef56b0 updated cred manager upgrade text with correct version 2018-12-30 11:32:24 -06:00
David Sparer
258093e52a when harvesting creds, consider Password when determining if a credential set is a duplicate
This prevents issues when the username and domain are the same but passwords are different (such as for local admin accounts)
2018-12-28 16:25:33 -06:00
David Sparer
b09fdcd5f5 credential objects now used when establishing connections 2018-12-28 16:25:33 -06:00
David Sparer
2277e95dd2 fixed tests 2018-12-28 16:25:33 -06:00
David Sparer
e0486bec7d simplified some of the credential management classes 2018-12-28 16:25:33 -06:00
David Sparer
29d44d103d fixed bug preventing FullyObservableCollection from generating events 2018-12-28 16:25:32 -06:00
David Sparer
44aa100566 minor change to chkbox location 2018-12-28 16:25:32 -06:00
David Sparer
23eb9cc44b added option to auto close the cred unlocker form when all repos are unlocked 2018-12-28 16:25:32 -06:00
David Sparer
f1797282d9 readded option to unlock cred repos on startup 2018-12-28 16:25:32 -06:00
David Sparer
0c0740d488 cred upgrade form now only lets you upgrade if all required fields are filled in 2018-12-28 16:25:32 -06:00
David Sparer
a4faaa20c3 cred manager upgrader now correctly uses custom password provided during upgrade 2018-12-28 16:25:29 -06:00
David Sparer
ce03b74d48 serialization is now working as expected 2018-12-28 16:25:29 -06:00
David Sparer
ce8ada05f5 hooked up the credential manager upgrader 2018-12-28 16:25:29 -06:00
David Sparer
b4dfe5beb6 hooked up tools menu button for cred manager 2018-12-28 16:25:29 -06:00
David Sparer
22ecf0d06f readded forms and controls for the credential manager 2018-12-28 16:25:29 -06:00
David Sparer
1e66787422 readded the credential record property to the connection info class 2018-12-28 16:25:28 -06:00
215 changed files with 17223 additions and 3025 deletions

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using mRemoteNG.Config.Connections; using mRemoteNG.Config.Connections;
@@ -19,7 +19,7 @@ namespace mRemoteNG.App
{ {
public static class Export public static class Export
{ {
public static void ExportToFile(ConnectionInfo selectedNode, ConnectionTreeModel connectionTreeModel) public static void ExportToFile(ConnectionInfo selectedNode, IConnectionTreeModel connectionTreeModel)
{ {
try try
{ {
@@ -93,8 +93,7 @@ namespace mRemoteNG.App
serializer = new XmlConnectionsSerializer(cryptographyProvider, connectionNodeSerializer); serializer = new XmlConnectionsSerializer(cryptographyProvider, connectionNodeSerializer);
break; break;
case SaveFormat.mRCSV: case SaveFormat.mRCSV:
serializer = serializer = new CsvConnectionsSerializerMremotengFormat(saveFilter, Runtime.CredentialService.RepositoryList);
new CsvConnectionsSerializerMremotengFormat(saveFilter, Runtime.CredentialProviderCatalog);
break; break;
default: default:
throw new ArgumentOutOfRangeException(nameof(saveFormat), saveFormat, null); throw new ArgumentOutOfRangeException(nameof(saveFormat), saveFormat, null);

View File

@@ -10,7 +10,7 @@ using mRemoteNG.Tools;
namespace mRemoteNG.App namespace mRemoteNG.App
{ {
public static class Import public static class Import
{ {
public static void ImportFromFile(ContainerInfo importDestinationContainer) public static void ImportFromFile(ContainerInfo importDestinationContainer)
{ {

View File

@@ -1,21 +1,38 @@
using System.IO; using System;
using System.IO;
using System.Linq;
using mRemoteNG.Config.Connections; using mRemoteNG.Config.Connections;
using mRemoteNG.Connection;
using mRemoteNG.Credential;
using mRemoteNG.Tools;
using mRemoteNG.Properties; using mRemoteNG.Properties;
namespace mRemoteNG.App.Initialization namespace mRemoteNG.App.Initialization
{ {
public class CredsAndConsSetup public class CredsAndConsSetup
{ {
public void LoadCredsAndCons() public void LoadCredsAndCons(ConnectionsService connectionsService, CredentialService credentialService, SaveConnectionsOnEdit connectionsOnEdit)
{ {
new SaveConnectionsOnEdit(Runtime.ConnectionsService); connectionsOnEdit.Subscribe(connectionsService);
if (Settings.Default.FirstStart && !Settings.Default.LoadConsFromCustomLocation && if (Settings.Default.FirstStart && !Settings.Default.LoadConsFromCustomLocation &&
!File.Exists(Runtime.ConnectionsService.GetStartupConnectionFileName())) !File.Exists(connectionsService.GetStartupConnectionFileName()))
Runtime.ConnectionsService.NewConnectionsFile(Runtime.ConnectionsService connectionsService.NewConnectionsFile(connectionsService.GetStartupConnectionFileName());
.GetStartupConnectionFileName());
credentialService.LoadRepositoryList();
LoadDefaultConnectionCredentials(credentialService);
Runtime.LoadConnections(); Runtime.LoadConnections();
} }
private void LoadDefaultConnectionCredentials(CredentialService credentialService)
{
var defaultCredId = Settings.Default.ConDefaultCredentialRecord;
var matchedCredentials = credentialService
.GetCredentialRecords()
.Where(record => record.Id.Equals(defaultCredId))
.ToArray();
DefaultConnectionInfo.Instance.CredentialRecordId = Optional<Guid>.FromNullable(matchedCredentials.FirstOrDefault()?.Id);
}
} }
} }

View File

@@ -2,7 +2,6 @@
using mRemoteNG.Config.Putty; using mRemoteNG.Config.Putty;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.Credential; using mRemoteNG.Credential;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.Messages; using mRemoteNG.Messages;
using mRemoteNG.Security; using mRemoteNG.Security;
using mRemoteNG.Tools; using mRemoteNG.Tools;
@@ -36,7 +35,7 @@ namespace mRemoteNG.App
/// <summary> /// <summary>
/// Feature flag to enable the credential manager feature /// Feature flag to enable the credential manager feature
/// </summary> /// </summary>
public static bool UseCredentialManager => false; public static bool UseCredentialManager => true;
public static WindowList WindowList { get; set; } public static WindowList WindowList { get; set; }
public static MessageCollector MessageCollector { get; } = new MessageCollector(); public static MessageCollector MessageCollector { get; } = new MessageCollector();
@@ -46,10 +45,9 @@ namespace mRemoteNG.App
public static SecureString EncryptionKey { get; set; } = public static SecureString EncryptionKey { get; set; } =
new RootNodeInfo(RootNodeType.Connection).PasswordString.ConvertToSecureString(); new RootNodeInfo(RootNodeType.Connection).PasswordString.ConvertToSecureString();
public static ICredentialRepositoryList CredentialProviderCatalog { get; } = new CredentialRepositoryList(); public static CredentialService CredentialService { get; } = new CredentialServiceFactory().Build();
public static ConnectionsService ConnectionsService { get; } = public static ConnectionsService ConnectionsService { get; } = new ConnectionsService(PuttySessionsManager.Instance, CredentialService);
new ConnectionsService(PuttySessionsManager.Instance);
#region Connections Loading/Saving #region Connections Loading/Saving
@@ -96,7 +94,7 @@ namespace mRemoteNG.App
connectionFileName = ConnectionsService.GetStartupConnectionFileName(); connectionFileName = ConnectionsService.GetStartupConnectionFileName();
} }
ConnectionsService.LoadConnections(Settings.Default.UseSQLServer, false, connectionFileName); ConnectionsService.LoadConnections(Settings.Default.UseSQLServer, connectionFileName);
if (Settings.Default.UseSQLServer) if (Settings.Default.UseSQLServer)
{ {

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using mRemoteNG.Credential;
namespace mRemoteNG.Config
{
public class ConnectionToCredentialMap : Dictionary<Guid, ICredentialRecord>
{
private readonly IEqualityComparer<ICredentialRecord> _credentialComparer = new CredentialDomainUserPasswordComparer();
public IEnumerable<ICredentialRecord> DistinctCredentialRecords => Values.Distinct(_credentialComparer);
}
}

View File

@@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using mRemoteNG.Connection;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using mRemoteNG.Tree; using mRemoteNG.Tree;
@@ -10,7 +12,7 @@ namespace mRemoteNG.Config.Connections
/// The previous <see cref="ConnectionTreeModel"/> that is being /// The previous <see cref="ConnectionTreeModel"/> that is being
/// unloaded. /// unloaded.
/// </summary> /// </summary>
public Optional<ConnectionTreeModel> PreviousConnectionTreeModel { get; } public List<ConnectionInfo> RemovedConnections { get; }
/// <summary> /// <summary>
/// True if the previous <see cref="ConnectionTreeModel"/> was loaded from /// True if the previous <see cref="ConnectionTreeModel"/> was loaded from
@@ -21,7 +23,7 @@ namespace mRemoteNG.Config.Connections
/// <summary> /// <summary>
/// The new <see cref="ConnectionTreeModel"/> that is being loaded. /// The new <see cref="ConnectionTreeModel"/> that is being loaded.
/// </summary> /// </summary>
public ConnectionTreeModel NewConnectionTreeModel { get; } public List<ConnectionInfo> AddedConnections { get; }
/// <summary> /// <summary>
/// True if the new <see cref="ConnectionTreeModel"/> was loaded from /// True if the new <see cref="ConnectionTreeModel"/> was loaded from
@@ -36,24 +38,18 @@ namespace mRemoteNG.Config.Connections
/// </summary> /// </summary>
public string NewSourcePath { get; } public string NewSourcePath { get; }
public ConnectionsLoadedEventArgs(Optional<ConnectionTreeModel> previousTreeModelModel, public IConnectionTreeModel NewConnectionTreeModel { get; set; } = new ConnectionTreeModel();
ConnectionTreeModel newTreeModelModel,
bool previousSourceWasDatabase,
bool newSourceIsDatabase,
string newSourcePath)
{
if (previousTreeModelModel == null)
throw new ArgumentNullException(nameof(previousTreeModelModel));
if (newTreeModelModel == null)
throw new ArgumentNullException(nameof(newTreeModelModel));
if (newSourcePath == null)
throw new ArgumentNullException(nameof(newSourcePath));
PreviousConnectionTreeModel = previousTreeModelModel; public ConnectionsLoadedEventArgs(
List<ConnectionInfo> removedConnections, List<ConnectionInfo> addedConnections,
bool previousSourceWasDatabase, bool newSourceIsDatabase,
string newSourcePath)
{
RemovedConnections = removedConnections.ThrowIfNull(nameof(removedConnections));
PreviousSourceWasDatabase = previousSourceWasDatabase; PreviousSourceWasDatabase = previousSourceWasDatabase;
NewConnectionTreeModel = newTreeModelModel; AddedConnections = addedConnections.ThrowIfNull(nameof(addedConnections));
NewSourceIsDatabase = newSourceIsDatabase; NewSourceIsDatabase = newSourceIsDatabase;
NewSourcePath = newSourcePath; NewSourcePath = newSourcePath.ThrowIfNull(nameof(newSourcePath));
} }
} }
} }

View File

@@ -5,12 +5,12 @@ namespace mRemoteNG.Config.Connections
{ {
public class ConnectionsSavedEventArgs public class ConnectionsSavedEventArgs
{ {
public ConnectionTreeModel ModelThatWasSaved { get; } public IConnectionTreeModel ModelThatWasSaved { get; }
public bool PreviouslyUsingDatabase { get; } public bool PreviouslyUsingDatabase { get; }
public bool UsingDatabase { get; } public bool UsingDatabase { get; }
public string ConnectionFileName { get; } public string ConnectionFileName { get; }
public ConnectionsSavedEventArgs(ConnectionTreeModel modelThatWasSaved, public ConnectionsSavedEventArgs(IConnectionTreeModel modelThatWasSaved,
bool previouslyUsingDatabase, bool previouslyUsingDatabase,
bool usingDatabase, bool usingDatabase,
string connectionFileName) string connectionFileName)

View File

@@ -25,8 +25,7 @@ namespace mRemoteNG.Config.Connections
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "") public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{ {
var csvConnectionsSerializer = var csvConnectionsSerializer = new CsvConnectionsSerializerMremotengFormat(_saveFilter, Runtime.CredentialService.RepositoryList);
new CsvConnectionsSerializerMremotengFormat(_saveFilter, Runtime.CredentialProviderCatalog);
var dataProvider = new FileDataProvider(_connectionFileName); var dataProvider = new FileDataProvider(_connectionFileName);
var csvContent = csvConnectionsSerializer.Serialize(connectionTreeModel); var csvContent = csvConnectionsSerializer.Serialize(connectionTreeModel);
dataProvider.Save(csvContent); dataProvider.Save(csvContent);

View File

@@ -1,9 +1,9 @@
using mRemoteNG.Tree; using mRemoteNG.Config.Serializers;
namespace mRemoteNG.Config.Connections namespace mRemoteNG.Config.Connections
{ {
public interface IConnectionsLoader public interface IConnectionsLoader
{ {
ConnectionTreeModel Load(); SerializationResult Load();
} }
} }

View File

@@ -35,7 +35,7 @@ namespace mRemoteNG.Config.Connections.Multiuser
private void Load(object sender, ConnectionsUpdateAvailableEventArgs args) private void Load(object sender, ConnectionsUpdateAvailableEventArgs args)
{ {
Runtime.ConnectionsService.LoadConnections(true, false, ""); Runtime.ConnectionsService.LoadConnections(true, "");
args.Handled = true; args.Handled = true;
} }

View File

@@ -1,36 +1,26 @@
using System; using System.Collections.Specialized;
using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.UI.Forms; using mRemoteNG.Tools;
namespace mRemoteNG.Config.Connections namespace mRemoteNG.Config.Connections
{ {
public class SaveConnectionsOnEdit public class SaveConnectionsOnEdit
{ {
private readonly ConnectionsService _connectionsService; private IConnectionsService _connectionsService;
public SaveConnectionsOnEdit(ConnectionsService connectionsService) public void Subscribe(IConnectionsService connectionsService)
{ {
if (connectionsService == null) _connectionsService = connectionsService.ThrowIfNull(nameof(connectionsService));
throw new ArgumentNullException(nameof(connectionsService)); connectionsService.ConnectionTreeModel.CollectionChanged += ConnectionTreeModelOnCollectionChanged;
connectionsService.ConnectionTreeModel.PropertyChanged += ConnectionTreeModelOnPropertyChanged;
_connectionsService = connectionsService;
connectionsService.ConnectionsLoaded += ConnectionsServiceOnConnectionsLoaded;
} }
private void ConnectionsServiceOnConnectionsLoaded(object sender, public void Unsubscribe()
ConnectionsLoadedEventArgs connectionsLoadedEventArgs)
{ {
connectionsLoadedEventArgs.NewConnectionTreeModel.CollectionChanged += _connectionsService.ConnectionTreeModel.CollectionChanged -= ConnectionTreeModelOnCollectionChanged;
ConnectionTreeModelOnCollectionChanged; _connectionsService.ConnectionTreeModel.PropertyChanged -= ConnectionTreeModelOnPropertyChanged;
connectionsLoadedEventArgs.NewConnectionTreeModel.PropertyChanged += ConnectionTreeModelOnPropertyChanged; _connectionsService = null;
foreach (var oldTree in connectionsLoadedEventArgs.PreviousConnectionTreeModel)
{
oldTree.CollectionChanged -= ConnectionTreeModelOnCollectionChanged;
oldTree.PropertyChanged -= ConnectionTreeModelOnPropertyChanged;
}
} }
private void ConnectionTreeModelOnPropertyChanged(object sender, private void ConnectionTreeModelOnPropertyChanged(object sender,
@@ -50,10 +40,8 @@ namespace mRemoteNG.Config.Connections
{ {
if (!Properties.Settings.Default.SaveConnectionsAfterEveryEdit) if (!Properties.Settings.Default.SaveConnectionsAfterEveryEdit)
return; return;
if (FrmMain.Default.IsClosing)
return;
_connectionsService.SaveConnectionsAsync(propertyName); _connectionsService?.SaveConnectionsAsync(propertyName);
} }
} }
} }

View File

@@ -12,7 +12,6 @@ using mRemoteNG.Security;
using mRemoteNG.Security.Authentication; using mRemoteNG.Security.Authentication;
using mRemoteNG.Security.SymmetricEncryption; using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root; using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections namespace mRemoteNG.Config.Connections
@@ -36,7 +35,7 @@ namespace mRemoteNG.Config.Connections
_dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider)); _dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
} }
public ConnectionTreeModel Load() public SerializationResult Load()
{ {
var connector = DatabaseConnectorFactory.DatabaseConnectorFromSettings(); var connector = DatabaseConnectorFactory.DatabaseConnectorFromSettings();
var dataProvider = new SqlDataProvider(connector); var dataProvider = new SqlDataProvider(connector);
@@ -54,9 +53,9 @@ namespace mRemoteNG.Config.Connections
databaseVersionVerifier.VerifyDatabaseVersion(metaData.ConfVersion); databaseVersionVerifier.VerifyDatabaseVersion(metaData.ConfVersion);
var dataTable = dataProvider.Load(); var dataTable = dataProvider.Load();
var deserializer = new DataTableDeserializer(cryptoProvider, decryptionKey.First()); var deserializer = new DataTableDeserializer(cryptoProvider, decryptionKey.First());
var connectionTree = deserializer.Deserialize(dataTable); var serializationResult = deserializer.Deserialize(dataTable);
ApplyLocalConnectionProperties(connectionTree.RootNodes.First(i => i is RootNodeInfo)); ApplyLocalConnectionProperties(serializationResult.ConnectionRecords.OfType<RootNodeInfo>().First());
return connectionTree; return serializationResult;
} }
private Optional<SecureString> GetDecryptionKey(SqlConnectionListMetaData metaData) private Optional<SecureString> GetDecryptionKey(SqlConnectionListMetaData metaData)

View File

@@ -20,7 +20,7 @@ using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections namespace mRemoteNG.Config.Connections
{ {
public class SqlConnectionsSaver : ISaver<ConnectionTreeModel> public class SqlConnectionsSaver : ISaver<IConnectionTreeModel>
{ {
private readonly SaveFilter _saveFilter; private readonly SaveFilter _saveFilter;
private readonly ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> _localPropertiesSerializer; private readonly ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> _localPropertiesSerializer;
@@ -38,7 +38,7 @@ namespace mRemoteNG.Config.Connections
_dataProvider = localPropertiesDataProvider.ThrowIfNull(nameof(localPropertiesDataProvider)); _dataProvider = localPropertiesDataProvider.ThrowIfNull(nameof(localPropertiesDataProvider));
} }
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "") public void Save(IConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{ {
var rootTreeNode = connectionTreeModel.RootNodes.OfType<RootNodeInfo>().First(); var rootTreeNode = connectionTreeModel.RootNodes.OfType<RootNodeInfo>().First();

View File

@@ -1,18 +1,25 @@
using mRemoteNG.Config.DataProviders; using mRemoteNG.Config.DataProviders;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using mRemoteNG.Tree;
using System; using System;
using System.IO; using System.IO;
using System.Security; using System.Security;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml; using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
using mRemoteNG.App.Info;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Connection;
using mRemoteNG.Credential;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.Config.Connections namespace mRemoteNG.Config.Connections
{ {
public class XmlConnectionsLoader : IConnectionsLoader public class XmlConnectionsLoader : IConnectionsLoader
{ {
private readonly string _credentialFilePath = Path.Combine(CredentialsFileInfo.CredentialsPath, CredentialsFileInfo.CredentialsFile);
private readonly string _connectionFilePath; private readonly string _connectionFilePath;
private readonly ConnectionsService _connectionsService;
private readonly ICredentialService _credentialService;
public XmlConnectionsLoader(string connectionFilePath) public XmlConnectionsLoader(string connectionFilePath, ICredentialService credentialService, ConnectionsService connectionsService)
{ {
if (string.IsNullOrEmpty(connectionFilePath)) if (string.IsNullOrEmpty(connectionFilePath))
throw new ArgumentException($"{nameof(connectionFilePath)} cannot be null or empty"); throw new ArgumentException($"{nameof(connectionFilePath)} cannot be null or empty");
@@ -21,14 +28,26 @@ namespace mRemoteNG.Config.Connections
throw new FileNotFoundException($"{connectionFilePath} does not exist"); throw new FileNotFoundException($"{connectionFilePath} does not exist");
_connectionFilePath = connectionFilePath; _connectionFilePath = connectionFilePath;
_connectionsService = connectionsService.ThrowIfNull(nameof(connectionsService));
_credentialService = credentialService.ThrowIfNull(nameof(credentialService));
} }
public ConnectionTreeModel Load() public SerializationResult Load()
{ {
var dataProvider = new FileDataProvider(_connectionFilePath); var dataProvider = new FileDataProvider(_connectionFilePath);
var xmlString = dataProvider.Load(); var xmlString = dataProvider.Load();
var deserializer = new XmlConnectionsDeserializer(PromptForPassword); var deserializer = new CredentialManagerUpgradeForm
return deserializer.Deserialize(xmlString); {
ConnectionFilePath = _connectionFilePath,
NewCredentialRepoPath = _credentialFilePath,
ConnectionsService = _connectionsService,
CredentialService = _credentialService,
ConnectionDeserializer = new XmlConnectionsDeserializer(PromptForPassword)
};
var serializationResult = deserializer.Deserialize(xmlString);
return serializationResult;
} }
private Optional<SecureString> PromptForPassword() private Optional<SecureString> PromptForPassword()

View File

@@ -10,7 +10,7 @@ using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections namespace mRemoteNG.Config.Connections
{ {
public class XmlConnectionsSaver : ISaver<ConnectionTreeModel> public class XmlConnectionsSaver : ISaver<IConnectionTreeModel>
{ {
private readonly string _connectionFileName; private readonly string _connectionFileName;
private readonly SaveFilter _saveFilter; private readonly SaveFilter _saveFilter;
@@ -26,7 +26,7 @@ namespace mRemoteNG.Config.Connections
_saveFilter = saveFilter; _saveFilter = saveFilter;
} }
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "") public void Save(IConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{ {
try try
{ {

View File

@@ -0,0 +1,39 @@
using System.Collections.Generic;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.CredentialProviderSerializer;
using mRemoteNG.Credential;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.Tools;
namespace mRemoteNG.Config
{
public class CredentialRepositoryListPersistor : ISaver<IEnumerable<ICredentialRepository>>, ILoader<IEnumerable<ICredentialRepository>>
{
private readonly IReadOnlyCollection<ICredentialRepositoryFactory> _repositoryFactories;
private readonly IDataProvider<string> _dataProvider;
private readonly CredentialRepositoryListDeserializer _deserializer;
private readonly CredentialRepositoryListSerializer _serializer;
public CredentialRepositoryListPersistor(
IDataProvider<string> dataProvider,
IReadOnlyCollection<ICredentialRepositoryFactory> repositoryFactories)
{
_repositoryFactories = repositoryFactories.ThrowIfNull(nameof(repositoryFactories));
_dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
_deserializer = new CredentialRepositoryListDeserializer();
_serializer = new CredentialRepositoryListSerializer();
}
public IEnumerable<ICredentialRepository> Load()
{
var data = _dataProvider.Load();
return _deserializer.Deserialize(data, _repositoryFactories);
}
public void Save(IEnumerable<ICredentialRepository> repositories, string propertyNameTrigger = "")
{
var data = _serializer.Serialize(repositories);
_dataProvider.Save(data);
}
}
}

View File

@@ -1,10 +1,11 @@
using System.IO; using mRemoteNG.App;
using System.Linq;
using mRemoteNG.App;
using mRemoteNG.Config.DataProviders; using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Csv; using mRemoteNG.Config.Serializers.ConnectionSerializers.Csv;
using mRemoteNG.Container; using mRemoteNG.Container;
using mRemoteNG.Messages; using mRemoteNG.Messages;
using System.IO;
using System.Linq;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.Config.Import namespace mRemoteNG.Config.Import
{ {
@@ -25,10 +26,17 @@ namespace mRemoteNG.Config.Import
var dataProvider = new FileDataProvider(filePath); var dataProvider = new FileDataProvider(filePath);
var xmlString = dataProvider.Load(); var xmlString = dataProvider.Load();
var xmlConnectionsDeserializer = new CsvConnectionsDeserializerMremotengFormat(); var xmlConnectionsDeserializer = new CsvConnectionsDeserializerMremotengFormat();
var connectionTreeModel = xmlConnectionsDeserializer.Deserialize(xmlString); var serializationResult = xmlConnectionsDeserializer.Deserialize(xmlString);
var credentialImportForm = new CredentialImportForm
{
ImportedCredentialRecords = serializationResult.ConnectionToCredentialMap.DistinctCredentialRecords.ToList(),
CredentialService = Runtime.CredentialService
};
credentialImportForm.ShowDialog();
var rootImportContainer = new ContainerInfo {Name = Path.GetFileNameWithoutExtension(filePath)}; var rootImportContainer = new ContainerInfo {Name = Path.GetFileNameWithoutExtension(filePath)};
rootImportContainer.AddChildRange(connectionTreeModel.RootNodes.First().Children.ToArray()); rootImportContainer.AddChildRange(serializationResult.ConnectionRecords);
destinationContainer.AddChild(rootImportContainer); destinationContainer.AddChild(rootImportContainer);
} }
} }

View File

@@ -1,5 +1,4 @@
using System.IO; using System.IO;
using System.Linq;
using mRemoteNG.App; using mRemoteNG.App;
using mRemoteNG.Config.DataProviders; using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml; using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
@@ -27,10 +26,10 @@ namespace mRemoteNG.Config.Import
var dataProvider = new FileDataProvider(fileName); var dataProvider = new FileDataProvider(fileName);
var xmlString = dataProvider.Load(); var xmlString = dataProvider.Load();
var xmlConnectionsDeserializer = new XmlConnectionsDeserializer(); var xmlConnectionsDeserializer = new XmlConnectionsDeserializer();
var connectionTreeModel = xmlConnectionsDeserializer.Deserialize(xmlString, true); var serializationResult = xmlConnectionsDeserializer.Deserialize(xmlString, true);
var rootImportContainer = new ContainerInfo {Name = Path.GetFileNameWithoutExtension(fileName)}; var rootImportContainer = new ContainerInfo {Name = Path.GetFileNameWithoutExtension(fileName)};
rootImportContainer.AddChildRange(connectionTreeModel.RootNodes.First().Children.ToArray()); rootImportContainer.AddChildRange(serializationResult.ConnectionRecords);
destinationContainer.AddChild(rootImportContainer); destinationContainer.AddChild(rootImportContainer);
} }
} }

View File

@@ -1,5 +1,4 @@
using System.Linq; using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.MiscSerializers; using mRemoteNG.Config.Serializers.MiscSerializers;
using mRemoteNG.Container; using mRemoteNG.Container;
@@ -14,12 +13,9 @@ namespace mRemoteNG.Config.Import
var xmlContent = dataProvider.Load(); var xmlContent = dataProvider.Load();
var deserializer = new PuttyConnectionManagerDeserializer(); var deserializer = new PuttyConnectionManagerDeserializer();
var connectionTreeModel = deserializer.Deserialize(xmlContent); var serializationResult = deserializer.Deserialize(xmlContent);
var importedRootNode = connectionTreeModel.RootNodes.First(); destinationContainer.AddChildRange(serializationResult.ConnectionRecords);
if (importedRootNode == null) return;
var childrenToAdd = importedRootNode.Children.ToArray();
destinationContainer.AddChildRange(childrenToAdd);
} }
} }
} }

View File

@@ -15,9 +15,9 @@ namespace mRemoteNG.Config.Import
var content = dataProvider.Load(); var content = dataProvider.Load();
var deserializer = new RemoteDesktopConnectionDeserializer(); var deserializer = new RemoteDesktopConnectionDeserializer();
var connectionTreeModel = deserializer.Deserialize(content); var serializationResult = deserializer.Deserialize(content);
var importedConnection = connectionTreeModel.RootNodes.First().Children.First(); var importedConnection = serializationResult.ConnectionRecords.FirstOrDefault();
if (importedConnection == null) return; if (importedConnection == null) return;
importedConnection.Name = Path.GetFileNameWithoutExtension(fileName); importedConnection.Name = Path.GetFileNameWithoutExtension(fileName);

View File

@@ -1,5 +1,4 @@
using System.Linq; using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.MiscSerializers; using mRemoteNG.Config.Serializers.MiscSerializers;
using mRemoteNG.Container; using mRemoteNG.Container;
@@ -14,12 +13,9 @@ namespace mRemoteNG.Config.Import
var fileContent = dataProvider.Load(); var fileContent = dataProvider.Load();
var deserializer = new RemoteDesktopConnectionManagerDeserializer(); var deserializer = new RemoteDesktopConnectionManagerDeserializer();
var connectionTreeModel = deserializer.Deserialize(fileContent); var serializationResult = deserializer.Deserialize(fileContent);
var importedRootNode = connectionTreeModel.RootNodes.First(); destinationContainer.AddChildRange(serializationResult.ConnectionRecords);
if (importedRootNode == null) return;
var childrenToAdd = importedRootNode.Children.ToArray();
destinationContainer.AddChildRange(childrenToAdd);
} }
} }
} }

View File

@@ -1,13 +1,13 @@
using System; using Microsoft.Win32;
using System.Collections.Generic;
using System.Management;
using System.Net;
using System.Security.Principal;
using Microsoft.Win32;
using mRemoteNG.App; using mRemoteNG.App;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol;
using mRemoteNG.Messages; using mRemoteNG.Messages;
using System;
using System.Collections.Generic;
using System.Management;
using System.Net;
using System.Security.Principal;
namespace mRemoteNG.Config.Putty namespace mRemoteNG.Config.Putty
@@ -55,6 +55,7 @@ namespace mRemoteNG.Config.Putty
PuttySession = sessionName, PuttySession = sessionName,
Name = sessionName, Name = sessionName,
Hostname = sessionKey.GetValue("HostName")?.ToString() ?? "", Hostname = sessionKey.GetValue("HostName")?.ToString() ?? "",
// TODO: this should create a temp putty credential
Username = sessionKey.GetValue("UserName")?.ToString() ?? "" Username = sessionKey.GetValue("UserName")?.ToString() ?? ""
}; };

View File

@@ -1,20 +1,23 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security;
using mRemoteNG.Config.Serializers.CredentialSerializer;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.Http; using mRemoteNG.Connection.Protocol.Http;
using mRemoteNG.Connection.Protocol.RDP; using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Connection.Protocol.VNC; using mRemoteNG.Connection.Protocol.VNC;
using mRemoteNG.Container; using mRemoteNG.Container;
using mRemoteNG.Security;
using mRemoteNG.Tools;
using mRemoteNG.Tree; using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
{ {
public class CsvConnectionsDeserializerMremotengFormat : IDeserializer<string, ConnectionTreeModel> public class CsvConnectionsDeserializerMremotengFormat
{ {
public ConnectionTreeModel Deserialize(string serializedData) public SerializationResult Deserialize(string serializedData)
{ {
var lines = serializedData.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries); var lines = serializedData.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries);
var csvHeaders = new List<string>(); var csvHeaders = new List<string>();
@@ -29,26 +32,39 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
else else
{ {
var connectionInfo = ParseConnectionInfo(csvHeaders, line); var connectionInfo = ParseConnectionInfo(csvHeaders, line);
parentMapping.Add(connectionInfo, line[csvHeaders.IndexOf("Parent")]);
var parentFieldIndex = csvHeaders.IndexOf("Parent");
if (parentFieldIndex > 0)
parentMapping.Add(connectionInfo, line[parentFieldIndex]);
} }
} }
var root = CreateTreeStructure(parentMapping); var root = CreateTreeStructure(parentMapping);
var connectionTreeModel = new ConnectionTreeModel(); var harvestedCredentials = new CredentialHarvester()
connectionTreeModel.AddRootNode(root); .Harvest(new HarvestConfig<string[]>
return connectionTreeModel; {
ItemEnumerator = () => lines.Skip(1).Select(s => s.Split(';')),
ConnectionGuidSelector = line => csvHeaders.Contains("Id") ? Guid.Parse(line[csvHeaders.IndexOf("Id")]) : Guid.NewGuid(),
TitleSelector = line => "",
UsernameSelector = line => csvHeaders.Contains("Username") ? line[csvHeaders.IndexOf("Username")] : "",
DomainSelector = line => csvHeaders.Contains("Domain") ? line[csvHeaders.IndexOf("Domain")] : "",
PasswordSelector = line => csvHeaders.Contains("Password") ? line[csvHeaders.IndexOf("Password")].ConvertToSecureString() : new SecureString()
});
var result = new SerializationResult(root, harvestedCredentials);
return result;
} }
private RootNodeInfo CreateTreeStructure(Dictionary<ConnectionInfo, string> parentMapping) private List<ConnectionInfo> CreateTreeStructure(Dictionary<ConnectionInfo, string> parentMapping)
{ {
var root = new RootNodeInfo(RootNodeType.Connection); var root = new List<ConnectionInfo>();
foreach (var node in parentMapping) foreach (var node in parentMapping)
{ {
// no parent mapped, add to root // no parent mapped, add to root
if (string.IsNullOrEmpty(node.Value)) if (string.IsNullOrEmpty(node.Value))
{ {
root.AddChild(node.Key); root.Add(node.Key);
continue; continue;
} }
@@ -64,7 +80,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
} }
else else
{ {
root.AddChild(node.Key); root.Add(node.Key);
} }
} }
@@ -85,53 +101,25 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
? new ConnectionInfo(nodeId) ? new ConnectionInfo(nodeId)
: new ContainerInfo(nodeId); : new ContainerInfo(nodeId);
connectionRecord.Name = headers.Contains("Name") connectionRecord.Name = headers.Contains("Name") ? connectionCsv[headers.IndexOf("Name")] : "";
? connectionCsv[headers.IndexOf("Name")] connectionRecord.Description =
: ""; headers.Contains("Description") ? connectionCsv[headers.IndexOf("Description")] : "";
connectionRecord.Icon = headers.Contains("Icon") ? connectionCsv[headers.IndexOf("Icon")] : "";
connectionRecord.Panel = headers.Contains("Panel") ? connectionCsv[headers.IndexOf("Panel")] : "";
connectionRecord.Description = headers.Contains("Description") var hasCredRecordId = Guid.TryParse(
? connectionCsv[headers.IndexOf("Description")] headers.Contains("CredentialRecordId") ? connectionCsv[headers.IndexOf("CredentialRecordId")] : "",
: ""; out var credRecordId);
connectionRecord.CredentialRecordId = hasCredRecordId ? credRecordId : Optional<Guid>.Empty;
connectionRecord.Icon = headers.Contains("Icon") // TODO: harvest
? connectionCsv[headers.IndexOf("Icon")] connectionRecord.Username = headers.Contains("Username") ? connectionCsv[headers.IndexOf("Username")] : "";
: ""; connectionRecord.Password = headers.Contains("Password") ? connectionCsv[headers.IndexOf("Password")] : "";
connectionRecord.Domain = headers.Contains("Domain") ? connectionCsv[headers.IndexOf("Domain")] : "";
connectionRecord.Panel = headers.Contains("Panel")
? connectionCsv[headers.IndexOf("Panel")]
: "";
connectionRecord.Username = headers.Contains("Username")
? connectionCsv[headers.IndexOf("Username")]
: "";
connectionRecord.Password = headers.Contains("Password")
? connectionCsv[headers.IndexOf("Password")]
: "";
connectionRecord.Domain = headers.Contains("Domain")
? connectionCsv[headers.IndexOf("Domain")]
: "";
connectionRecord.Hostname = headers.Contains("Hostname")
? connectionCsv[headers.IndexOf("Hostname")]
: "";
connectionRecord.VmId = headers.Contains("VmId")
? connectionCsv[headers.IndexOf("VmId")] : "";
connectionRecord.SSHOptions =headers.Contains("SSHOptions")
? connectionCsv[headers.IndexOf("SSHOptions")]
: "";
connectionRecord.SSHTunnelConnectionName = headers.Contains("SSHTunnelConnectionName")
? connectionCsv[headers.IndexOf("SSHTunnelConnectionName")]
: "";
connectionRecord.PuttySession = headers.Contains("PuttySession")
? connectionCsv[headers.IndexOf("PuttySession")]
: "";
connectionRecord.Hostname = headers.Contains("Hostname") ? connectionCsv[headers.IndexOf("Hostname")] : "";
connectionRecord.PuttySession =
headers.Contains("PuttySession") ? connectionCsv[headers.IndexOf("PuttySession")] : "";
connectionRecord.LoadBalanceInfo = headers.Contains("LoadBalanceInfo") connectionRecord.LoadBalanceInfo = headers.Contains("LoadBalanceInfo")
? connectionCsv[headers.IndexOf("LoadBalanceInfo")] ? connectionCsv[headers.IndexOf("LoadBalanceInfo")]
: ""; : "";
@@ -186,6 +174,12 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
? connectionCsv[headers.IndexOf("RDGatewayHostname")] ? connectionCsv[headers.IndexOf("RDGatewayHostname")]
: ""; : "";
if (headers.Contains("CredentialId"))
{
if (Guid.TryParse(connectionCsv[headers.IndexOf("CredentialId")], out var credId))
connectionRecord.CredentialRecordId = credId;
}
if (headers.Contains("Protocol")) if (headers.Contains("Protocol"))
{ {
if (Enum.TryParse(connectionCsv[headers.IndexOf("Protocol")], out ProtocolType protocolType)) if (Enum.TryParse(connectionCsv[headers.IndexOf("Protocol")], out ProtocolType protocolType))
@@ -825,6 +819,13 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
connectionRecord.Inheritance.RdpVersion = value; connectionRecord.Inheritance.RdpVersion = value;
} }
if (headers.Contains("InheritCredentialRecord"))
{
if (bool.TryParse(connectionCsv[headers.IndexOf("InheritCredentialRecord")], out bool value))
connectionRecord.Inheritance.CredentialId = value;
}
#endregion #endregion
return connectionRecord; return connectionRecord;

View File

@@ -61,7 +61,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
"CacheBitmaps;RedirectDiskDrives;RedirectPorts;RedirectPrinters;RedirectClipboard;RedirectSmartCards;RedirectSound;RedirectKeys;" + "CacheBitmaps;RedirectDiskDrives;RedirectPorts;RedirectPrinters;RedirectClipboard;RedirectSmartCards;RedirectSound;RedirectKeys;" +
"PreExtApp;PostExtApp;MacAddress;UserField;ExtApp;Favorite;VNCCompression;VNCEncoding;VNCAuthMode;VNCProxyType;VNCProxyIP;" + "PreExtApp;PostExtApp;MacAddress;UserField;ExtApp;Favorite;VNCCompression;VNCEncoding;VNCAuthMode;VNCProxyType;VNCProxyIP;" +
"VNCProxyPort;VNCProxyUsername;VNCProxyPassword;VNCColors;VNCSmartSizeMode;VNCViewOnly;RDGatewayUsageMethod;RDGatewayHostname;" + "VNCProxyPort;VNCProxyUsername;VNCProxyPassword;VNCColors;VNCSmartSizeMode;VNCViewOnly;RDGatewayUsageMethod;RDGatewayHostname;" +
"RDGatewayUseConnectionCredentials;RDGatewayUsername;RDGatewayPassword;RDGatewayDomain;RedirectAudioCapture;RdpVersion;"); "RDGatewayUseConnectionCredentials;RDGatewayUsername;RDGatewayPassword;RDGatewayDomain;RedirectAudioCapture;RdpVersion;CredentialId");
if (_saveFilter.SaveInheritance) if (_saveFilter.SaveInheritance)
sb.Append("InheritCacheBitmaps;InheritColors;InheritDescription;InheritDisplayThemes;InheritDisplayWallpaper;" + sb.Append("InheritCacheBitmaps;InheritColors;InheritDescription;InheritDisplayThemes;InheritDisplayWallpaper;" +
@@ -74,7 +74,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
"InheritVNCProxyPort;InheritVNCProxyUsername;InheritVNCProxyPassword;InheritVNCColors;InheritVNCSmartSizeMode;InheritVNCViewOnly;" + "InheritVNCProxyPort;InheritVNCProxyUsername;InheritVNCProxyPassword;InheritVNCColors;InheritVNCSmartSizeMode;InheritVNCViewOnly;" +
"InheritRDGatewayUsageMethod;InheritRDGatewayHostname;InheritRDGatewayUseConnectionCredentials;InheritRDGatewayUsername;" + "InheritRDGatewayUsageMethod;InheritRDGatewayHostname;InheritRDGatewayUseConnectionCredentials;InheritRDGatewayUsername;" +
"InheritRDGatewayPassword;InheritRDGatewayDomain;InheritRDPAlertIdleTimeout;InheritRDPMinutesToIdleTimeout;InheritSoundQuality;" + "InheritRDGatewayPassword;InheritRDGatewayDomain;InheritRDPAlertIdleTimeout;InheritRDPMinutesToIdleTimeout;InheritSoundQuality;" +
"InheritRedirectAudioCapture;InheritRdpVersion"); "InheritRedirectAudioCapture;InheritRdpVersion;InheritCredentialRecord");
} }
private void SerializeNodesRecursive(ConnectionInfo node, StringBuilder sb) private void SerializeNodesRecursive(ConnectionInfo node, StringBuilder sb)
@@ -104,18 +104,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
.Append(FormatForCsv(con.GetTreeNodeType())) .Append(FormatForCsv(con.GetTreeNodeType()))
.Append(FormatForCsv(con.Description)) .Append(FormatForCsv(con.Description))
.Append(FormatForCsv(con.Icon)) .Append(FormatForCsv(con.Icon))
.Append(FormatForCsv(con.Panel)); .Append(FormatForCsv(con.Panel))
.Append(FormatForCsv(con.Hostname))
if (_saveFilter.SaveUsername)
sb.Append(FormatForCsv(con.Username));
if (_saveFilter.SavePassword)
sb.Append(FormatForCsv(con.Password));
if (_saveFilter.SaveDomain)
sb.Append(FormatForCsv(con.Domain));
sb.Append(FormatForCsv(con.Hostname))
.Append(FormatForCsv(con.Port)) .Append(FormatForCsv(con.Port))
.Append(FormatForCsv(con.VmId)) .Append(FormatForCsv(con.VmId))
.Append(FormatForCsv(con.Protocol)) .Append(FormatForCsv(con.Protocol))
@@ -172,7 +162,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
.Append(FormatForCsv(con.RDGatewayPassword)) .Append(FormatForCsv(con.RDGatewayPassword))
.Append(FormatForCsv(con.RDGatewayDomain)) .Append(FormatForCsv(con.RDGatewayDomain))
.Append(FormatForCsv(con.RedirectAudioCapture)) .Append(FormatForCsv(con.RedirectAudioCapture))
.Append(FormatForCsv(con.RdpVersion)); .Append(FormatForCsv(con.RdpVersion))
.Append(FormatForCsv(con.CredentialRecordId));
if (!_saveFilter.SaveInheritance) if (!_saveFilter.SaveInheritance)
@@ -243,7 +234,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
.Append(FormatForCsv(con.Inheritance.RDPMinutesToIdleTimeout)) .Append(FormatForCsv(con.Inheritance.RDPMinutesToIdleTimeout))
.Append(FormatForCsv(con.Inheritance.SoundQuality)) .Append(FormatForCsv(con.Inheritance.SoundQuality))
.Append(FormatForCsv(con.Inheritance.RedirectAudioCapture)) .Append(FormatForCsv(con.Inheritance.RedirectAudioCapture))
.Append(FormatForCsv(con.Inheritance.RdpVersion)); .Append(FormatForCsv(con.Inheritance.RdpVersion))
.Append(FormatForCsv(con.Inheritance.CredentialId));
} }
private string FormatForCsv(object value) private string FormatForCsv(object value)

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
using System.Security; using System.Security;
using mRemoteNG.App;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.Http; using mRemoteNG.Connection.Protocol.Http;
@@ -12,12 +11,10 @@ using mRemoteNG.Connection.Protocol.VNC;
using mRemoteNG.Container; using mRemoteNG.Container;
using mRemoteNG.Security; using mRemoteNG.Security;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
{ {
public class DataTableDeserializer : IDeserializer<DataTable, ConnectionTreeModel> public class DataTableDeserializer
{ {
private readonly ICryptographyProvider _cryptographyProvider; private readonly ICryptographyProvider _cryptographyProvider;
private readonly SecureString _decryptionKey; private readonly SecureString _decryptionKey;
@@ -28,12 +25,13 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
_decryptionKey = decryptionKey.ThrowIfNull(nameof(decryptionKey)); _decryptionKey = decryptionKey.ThrowIfNull(nameof(decryptionKey));
} }
public ConnectionTreeModel Deserialize(DataTable table) public SerializationResult Deserialize(DataTable table)
{ {
var connectionList = CreateNodesFromTable(table); var connectionList = CreateNodesFromTable(table);
var connectionTreeModel = CreateNodeHierarchy(connectionList, table); var rootNodes = CreateNodeHierarchy(connectionList, table);
Runtime.ConnectionsService.IsConnectionsFileLoaded = true;
return connectionTreeModel; var serializationResult = new SerializationResult(rootNodes, new ConnectionToCredentialMap());
return serializationResult;
} }
private List<ConnectionInfo> CreateNodesFromTable(DataTable table) private List<ConnectionInfo> CreateNodesFromTable(DataTable table)
@@ -60,7 +58,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
{ {
var connectionId = row["ConstantID"] as string ?? Guid.NewGuid().ToString(); var connectionId = row["ConstantID"] as string ?? Guid.NewGuid().ToString();
var connectionInfo = new ConnectionInfo(connectionId); var connectionInfo = new ConnectionInfo(connectionId);
PopulateConnectionInfoFromDatarow(row, connectionInfo); PopulateConnectionInfoFromDataRow(row, connectionInfo);
return connectionInfo; return connectionInfo;
} }
@@ -68,11 +66,11 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
{ {
var containerId = row["ConstantID"] as string ?? Guid.NewGuid().ToString(); var containerId = row["ConstantID"] as string ?? Guid.NewGuid().ToString();
var containerInfo = new ContainerInfo(containerId); var containerInfo = new ContainerInfo(containerId);
PopulateConnectionInfoFromDatarow(row, containerInfo); PopulateConnectionInfoFromDataRow(row, containerInfo);
return containerInfo; return containerInfo;
} }
private void PopulateConnectionInfoFromDatarow(DataRow dataRow, ConnectionInfo connectionInfo) private void PopulateConnectionInfoFromDataRow(DataRow dataRow, ConnectionInfo connectionInfo)
{ {
connectionInfo.Name = (string)dataRow["Name"]; connectionInfo.Name = (string)dataRow["Name"];
@@ -83,9 +81,15 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
connectionInfo.Description = (string)dataRow["Description"]; connectionInfo.Description = (string)dataRow["Description"];
connectionInfo.Icon = (string)dataRow["Icon"]; connectionInfo.Icon = (string)dataRow["Icon"];
connectionInfo.Panel = (string)dataRow["Panel"]; connectionInfo.Panel = (string)dataRow["Panel"];
connectionInfo.Username = (string)dataRow["Username"];
connectionInfo.Domain = (string)dataRow["Domain"]; // TODO: harvest
connectionInfo.Password = DecryptValue((string)dataRow["Password"]); if (dataRow.Table.Columns.Contains("Username"))
connectionInfo.Username = (string)dataRow["Username"];
if (dataRow.Table.Columns.Contains("DomainName"))
connectionInfo.Domain = (string)dataRow["DomainName"];
if (dataRow.Table.Columns.Contains("Password"))
connectionInfo.Password = DecryptValue((string)dataRow["Password"]);
connectionInfo.Hostname = (string)dataRow["Hostname"]; connectionInfo.Hostname = (string)dataRow["Hostname"];
connectionInfo.VmId = (string)dataRow["VmId"]; connectionInfo.VmId = (string)dataRow["VmId"];
connectionInfo.UseEnhancedMode = (bool)dataRow["UseEnhancedMode"]; connectionInfo.UseEnhancedMode = (bool)dataRow["UseEnhancedMode"];
@@ -184,7 +188,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
connectionInfo.Inheritance.Domain = (bool)dataRow["InheritDomain"]; connectionInfo.Inheritance.Domain = (bool)dataRow["InheritDomain"];
connectionInfo.Inheritance.Icon = (bool)dataRow["InheritIcon"]; connectionInfo.Inheritance.Icon = (bool)dataRow["InheritIcon"];
connectionInfo.Inheritance.Panel = (bool)dataRow["InheritPanel"]; connectionInfo.Inheritance.Panel = (bool)dataRow["InheritPanel"];
connectionInfo.Inheritance.Password = (bool)dataRow["InheritPassword"];
connectionInfo.Inheritance.Port = (bool)dataRow["InheritPort"]; connectionInfo.Inheritance.Port = (bool)dataRow["InheritPort"];
connectionInfo.Inheritance.Protocol = (bool)dataRow["InheritProtocol"]; connectionInfo.Inheritance.Protocol = (bool)dataRow["InheritProtocol"];
connectionInfo.Inheritance.SSHTunnelConnectionName = (bool)dataRow["InheritSSHTunnelConnectionName"]; connectionInfo.Inheritance.SSHTunnelConnectionName = (bool)dataRow["InheritSSHTunnelConnectionName"];
@@ -251,28 +254,21 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
} }
} }
private ConnectionTreeModel CreateNodeHierarchy(List<ConnectionInfo> connectionList, DataTable dataTable) private List<ConnectionInfo> CreateNodeHierarchy(IReadOnlyCollection<ConnectionInfo> connectionList, DataTable dataTable)
{ {
var connectionTreeModel = new ConnectionTreeModel(); var rootNodes = new List<ConnectionInfo>();
var rootNode = new RootNodeInfo(RootNodeType.Connection, "0")
{
PasswordString = _decryptionKey.ConvertToUnsecureString()
};
connectionTreeModel.AddRootNode(rootNode);
foreach (DataRow row in dataTable.Rows) foreach (DataRow row in dataTable.Rows)
{ {
var id = (string)row["ConstantID"]; var id = (string) row["ConstantID"];
var connectionInfo = connectionList.First(node => node.ConstantID == id); var connectionInfo = connectionList.First(node => node.ConstantID == id);
var parentId = (string)row["ParentID"]; var parentId = (string) row["ParentID"];
if (parentId == "0" || connectionList.All(node => node.ConstantID != parentId)) if (parentId == "0" || connectionList.All(node => node.ConstantID != parentId))
rootNode.AddChild(connectionInfo); rootNodes.Add(connectionInfo);
else else
(connectionList.First(node => node.ConstantID == parentId) as ContainerInfo)?.AddChild( (connectionList.First(node => node.ConstantID == parentId) as ContainerInfo)?.AddChild(connectionInfo);
connectionInfo);
} }
return rootNodes;
return connectionTreeModel;
} }
} }
} }

View File

@@ -105,9 +105,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataTable.Columns.Add("Description", typeof(string)); dataTable.Columns.Add("Description", typeof(string));
dataTable.Columns.Add("Icon", typeof(string)); dataTable.Columns.Add("Icon", typeof(string));
dataTable.Columns.Add("Panel", typeof(string)); dataTable.Columns.Add("Panel", typeof(string));
dataTable.Columns.Add("Username", typeof(string));
dataTable.Columns.Add("Domain", typeof(string));
dataTable.Columns.Add("Password", typeof(string));
dataTable.Columns.Add("Hostname", typeof(string)); dataTable.Columns.Add("Hostname", typeof(string));
dataTable.Columns.Add("Port", typeof(int)); dataTable.Columns.Add("Port", typeof(int));
dataTable.Columns.Add("Protocol", typeof(string)); dataTable.Columns.Add("Protocol", typeof(string));
@@ -502,16 +499,10 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataRow["ParentID"] = connectionInfo.Parent?.ConstantID ?? ""; dataRow["ParentID"] = connectionInfo.Parent?.ConstantID ?? "";
dataRow["PositionID"] = _currentNodeIndex; dataRow["PositionID"] = _currentNodeIndex;
dataRow["LastChange"] = MiscTools.DBTimeStampNow(); dataRow["LastChange"] = MiscTools.DBTimeStampNow();
dataRow["Expanded"] =
false; // TODO: this column can eventually be removed. we now save this property locally
dataRow["Description"] = connectionInfo.Description; dataRow["Description"] = connectionInfo.Description;
dataRow["Icon"] = connectionInfo.Icon; dataRow["Icon"] = connectionInfo.Icon;
dataRow["Panel"] = connectionInfo.Panel; dataRow["Panel"] = connectionInfo.Panel;
dataRow["Username"] = _saveFilter.SaveUsername ? connectionInfo.Username : "";
dataRow["Domain"] = _saveFilter.SaveDomain ? connectionInfo.Domain : "";
dataRow["Password"] = _saveFilter.SavePassword
? _cryptographyProvider.Encrypt(connectionInfo.Password, _encryptionKey)
: "";
dataRow["Hostname"] = connectionInfo.Hostname; dataRow["Hostname"] = connectionInfo.Hostname;
dataRow["VmId"] = connectionInfo.VmId; dataRow["VmId"] = connectionInfo.VmId;
dataRow["Protocol"] = connectionInfo.Protocol; dataRow["Protocol"] = connectionInfo.Protocol;
@@ -549,7 +540,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataRow["SoundQuality"] = connectionInfo.SoundQuality; dataRow["SoundQuality"] = connectionInfo.SoundQuality;
dataRow["RedirectAudioCapture"] = connectionInfo.RedirectAudioCapture; dataRow["RedirectAudioCapture"] = connectionInfo.RedirectAudioCapture;
dataRow["RedirectKeys"] = connectionInfo.RedirectKeys; dataRow["RedirectKeys"] = connectionInfo.RedirectKeys;
dataRow["Connected"] = false; // TODO: this column can eventually be removed. we now save this property locally
dataRow["PreExtApp"] = connectionInfo.PreExtApp; dataRow["PreExtApp"] = connectionInfo.PreExtApp;
dataRow["PostExtApp"] = connectionInfo.PostExtApp; dataRow["PostExtApp"] = connectionInfo.PostExtApp;
dataRow["MacAddress"] = connectionInfo.MacAddress; dataRow["MacAddress"] = connectionInfo.MacAddress;
@@ -592,7 +582,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataRow["InheritDomain"] = connectionInfo.Inheritance.Domain; dataRow["InheritDomain"] = connectionInfo.Inheritance.Domain;
dataRow["InheritIcon"] = connectionInfo.Inheritance.Icon; dataRow["InheritIcon"] = connectionInfo.Inheritance.Icon;
dataRow["InheritPanel"] = connectionInfo.Inheritance.Panel; dataRow["InheritPanel"] = connectionInfo.Inheritance.Panel;
dataRow["InheritPassword"] = connectionInfo.Inheritance.Password;
dataRow["InheritPort"] = connectionInfo.Inheritance.Port; dataRow["InheritPort"] = connectionInfo.Inheritance.Port;
dataRow["InheritProtocol"] = connectionInfo.Inheritance.Protocol; dataRow["InheritProtocol"] = connectionInfo.Inheritance.Protocol;
dataRow["InheritSSHTunnelConnectionName"] = connectionInfo.Inheritance.SSHTunnelConnectionName; dataRow["InheritSSHTunnelConnectionName"] = connectionInfo.Inheritance.SSHTunnelConnectionName;
@@ -660,7 +649,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataRow["InheritDomain"] = false; dataRow["InheritDomain"] = false;
dataRow["InheritIcon"] = false; dataRow["InheritIcon"] = false;
dataRow["InheritPanel"] = false; dataRow["InheritPanel"] = false;
dataRow["InheritPassword"] = false;
dataRow["InheritPort"] = false; dataRow["InheritPort"] = false;
dataRow["InheritProtocol"] = false; dataRow["InheritProtocol"] = false;
dataRow["InheritSSHTunnelConnectionName"] = false; dataRow["InheritSSHTunnelConnectionName"] = false;

View File

@@ -10,7 +10,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
{ {
public ISerializer<ConnectionInfo, string> Build( public ISerializer<ConnectionInfo, string> Build(
ICryptographyProvider cryptographyProvider, ICryptographyProvider cryptographyProvider,
ConnectionTreeModel connectionTreeModel, IConnectionTreeModel connectionTreeModel,
SaveFilter saveFilter = null, SaveFilter saveFilter = null,
bool useFullEncryption = false) bool useFullEncryption = false)
{ {

View File

@@ -17,17 +17,19 @@ using mRemoteNG.Tree;
using mRemoteNG.Tree.Root; using mRemoteNG.Tree.Root;
using mRemoteNG.UI.Forms; using mRemoteNG.UI.Forms;
using mRemoteNG.UI.TaskDialog; using mRemoteNG.UI.TaskDialog;
using System.Collections.Generic;
using mRemoteNG.Credential;
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
{ {
public class XmlConnectionsDeserializer : IDeserializer<string, ConnectionTreeModel> public class XmlConnectionsDeserializer
{ {
private XmlDocument _xmlDocument; private XmlDocument _xmlDocument;
private double _confVersion; private double _confVersion;
private XmlConnectionsDecryptor _decryptor; private XmlConnectionsDecryptor _decryptor;
private string ConnectionFileName = ""; private string ConnectionFileName = "";
private const double MaxSupportedConfVersion = 2.8; private const double MaxSupportedConfVersion = 2.7;
private readonly RootNodeInfo _rootNodeInfo = new RootNodeInfo(RootNodeType.Connection); private readonly CredentialDomainUserPasswordComparer _credentialComparer = new CredentialDomainUserPasswordComparer();
public Func<Optional<SecureString>> AuthenticationRequestor { get; set; } public Func<Optional<SecureString>> AuthenticationRequestor { get; set; }
@@ -36,12 +38,12 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
AuthenticationRequestor = authenticationRequestor; AuthenticationRequestor = authenticationRequestor;
} }
public ConnectionTreeModel Deserialize(string xml) public SerializationResult Deserialize(string xml)
{ {
return Deserialize(xml, false); return Deserialize(xml, false);
} }
public ConnectionTreeModel Deserialize(string xml, bool import) public SerializationResult Deserialize(string xml, bool import)
{ {
try try
{ {
@@ -49,17 +51,13 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
ValidateConnectionFileVersion(); ValidateConnectionFileVersion();
var rootXmlElement = _xmlDocument.DocumentElement; var rootXmlElement = _xmlDocument.DocumentElement;
InitializeRootNode(rootXmlElement); var rootNodeInfo = InitializeRootNode(rootXmlElement);
CreateDecryptor(_rootNodeInfo, rootXmlElement); _decryptor = CreateDecryptor(rootNodeInfo, rootXmlElement);
var connectionTreeModel = new ConnectionTreeModel();
connectionTreeModel.AddRootNode(_rootNodeInfo);
if (_confVersion > 1.3) if (_confVersion > 1.3)
{ {
var protectedString = _xmlDocument.DocumentElement?.Attributes["Protected"].Value; var protectedString = _xmlDocument.DocumentElement?.Attributes["Protected"].Value;
if (!_decryptor.ConnectionsFileIsAuthentic(protectedString, if (!_decryptor.ConnectionsFileIsAuthentic(protectedString, rootNodeInfo.PasswordString.ConvertToSecureString()))
_rootNodeInfo.PasswordString.ConvertToSecureString()))
{ {
return null; return null;
} }
@@ -75,12 +73,11 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
} }
} }
AddNodesFromXmlRecursive(_xmlDocument.DocumentElement, _rootNodeInfo); var credentialMap = new ConnectionToCredentialMap();
var rootNodes = AddNodesFromXmlRecursive(_xmlDocument.DocumentElement, credentialMap);
var serializationResult = new SerializationResult(rootNodes, credentialMap);
if (!import) return serializationResult;
Runtime.ConnectionsService.IsConnectionsFileLoaded = true;
return connectionTreeModel;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -92,8 +89,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
private void LoadXmlConnectionData(string connections) private void LoadXmlConnectionData(string connections)
{ {
CreateDecryptor(new RootNodeInfo(RootNodeType.Connection)); var legacyDecryptor = CreateDecryptor(new RootNodeInfo(RootNodeType.Connection));
connections = _decryptor.LegacyFullFileDecrypt(connections); connections = legacyDecryptor.LegacyFullFileDecrypt(connections);
_xmlDocument = new XmlDocument(); _xmlDocument = new XmlDocument();
if (connections != "") if (connections != "")
_xmlDocument.LoadXml(connections); _xmlDocument.LoadXml(connections);
@@ -134,13 +131,16 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
); );
} }
private void InitializeRootNode(XmlElement connectionsRootElement) private RootNodeInfo InitializeRootNode(XmlElement connectionsRootElement)
{ {
var rootNodeName = connectionsRootElement?.Attributes["Name"].Value.Trim(); var rootNodeName = connectionsRootElement?.Attributes["Name"].Value.Trim();
_rootNodeInfo.Name = rootNodeName; return new RootNodeInfo(RootNodeType.Connection)
{
Name = rootNodeName
};
} }
private void CreateDecryptor(RootNodeInfo rootNodeInfo, XmlElement connectionsRootElement = null) private XmlConnectionsDecryptor CreateDecryptor(RootNodeInfo rootNodeInfo, XmlElement connectionsRootElement = null)
{ {
if (_confVersion >= 2.6) if (_confVersion >= 2.6)
{ {
@@ -148,24 +148,27 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
var mode = connectionsRootElement.GetAttributeAsEnum<BlockCipherModes>("BlockCipherMode"); var mode = connectionsRootElement.GetAttributeAsEnum<BlockCipherModes>("BlockCipherMode");
var keyDerivationIterations = connectionsRootElement.GetAttributeAsInt("KdfIterations"); var keyDerivationIterations = connectionsRootElement.GetAttributeAsInt("KdfIterations");
_decryptor = new XmlConnectionsDecryptor(engine, mode, rootNodeInfo) return new XmlConnectionsDecryptor(engine, mode, rootNodeInfo)
{ {
AuthenticationRequestor = AuthenticationRequestor, AuthenticationRequestor = AuthenticationRequestor,
KeyDerivationIterations = keyDerivationIterations KeyDerivationIterations = keyDerivationIterations
}; };
} }
else
return new XmlConnectionsDecryptor(rootNodeInfo)
{ {
_decryptor = new XmlConnectionsDecryptor(_rootNodeInfo) AuthenticationRequestor = AuthenticationRequestor
{AuthenticationRequestor = AuthenticationRequestor}; };
}
} }
private void AddNodesFromXmlRecursive(XmlNode parentXmlNode, ContainerInfo parentContainer) private List<ConnectionInfo> AddNodesFromXmlRecursive(XmlNode parentXmlNode, ConnectionToCredentialMap credentialMap)
{ {
try try
{ {
if (!parentXmlNode.HasChildNodes) return; if (!parentXmlNode.HasChildNodes)
return new List<ConnectionInfo>();
var children = new List<ConnectionInfo>();
foreach (XmlNode xmlNode in parentXmlNode.ChildNodes) foreach (XmlNode xmlNode in parentXmlNode.ChildNodes)
{ {
var nodeType = xmlNode.GetAttributeAsEnum("Type", TreeNodeType.Connection); var nodeType = xmlNode.GetAttributeAsEnum("Type", TreeNodeType.Connection);
@@ -174,24 +177,27 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
switch (nodeType) switch (nodeType)
{ {
case TreeNodeType.Connection: case TreeNodeType.Connection:
var connectionInfo = GetConnectionInfoFromXml(xmlNode); var connectionInfo = GetConnectionInfoFromXml(xmlNode, credentialMap);
parentContainer.AddChild(connectionInfo); children.Add(connectionInfo);
break; break;
case TreeNodeType.Container: case TreeNodeType.Container:
var containerInfo = new ContainerInfo(); var containerInfo = new ContainerInfo();
if (_confVersion >= 0.9) if (_confVersion >= 0.9)
containerInfo.CopyFrom(GetConnectionInfoFromXml(xmlNode)); containerInfo.CopyFrom(GetConnectionInfoFromXml(xmlNode, credentialMap));
if (_confVersion >= 0.8) if (_confVersion >= 0.8)
{ {
containerInfo.IsExpanded = xmlNode.GetAttributeAsBool("Expanded"); containerInfo.IsExpanded = xmlNode.GetAttributeAsBool("Expanded");
} }
parentContainer.AddChild(containerInfo); var subChildren = AddNodesFromXmlRecursive(xmlNode, credentialMap);
AddNodesFromXmlRecursive(xmlNode, containerInfo); subChildren.ForEach(info => containerInfo.AddChild(info));
children.Add(containerInfo);
break; break;
} }
} }
return children;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -200,7 +206,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
} }
} }
private ConnectionInfo GetConnectionInfoFromXml(XmlNode xmlnode) private ConnectionInfo GetConnectionInfoFromXml(XmlNode xmlnode, ConnectionToCredentialMap credentialMap)
{ {
if (xmlnode?.Attributes == null) if (xmlnode?.Attributes == null)
return null; return null;
@@ -228,13 +234,21 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
: RDPResolutions.FitToWindow; : RDPResolutions.FitToWindow;
} }
if (!Runtime.UseCredentialManager || _confVersion <= 2.6) // 0.2 - 2.6 if (_confVersion <= 2.6) // 0.2 - 2.6
{ {
#pragma warning disable 618 var username = xmlnode.GetAttributeAsString("Username");
connectionInfo.Username = xmlnode.GetAttributeAsString("Username"); var domain = xmlnode.GetAttributeAsString("Domain");
connectionInfo.Password = _decryptor.Decrypt(xmlnode.GetAttributeAsString("Password"));
connectionInfo.Domain = xmlnode.GetAttributeAsString("Domain"); var cred = new CredentialRecord
#pragma warning restore 618 {
Title = domain.Length > 0 ? $"{domain}\\{username}" : username,
Username = username,
Domain = domain,
Password = _decryptor.Decrypt(xmlnode.GetAttributeAsString("Password")).ConvertToSecureString()
};
if (!_credentialComparer.Equals(cred, new NullCredentialRecord()))
connectionInfo.CredentialRecordId = cred.Id;
} }
} }
@@ -556,6 +570,12 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
connectionInfo.Inheritance.DisableMenuAnimations = xmlnode.GetAttributeAsBool("InheritDisableMenuAnimations"); connectionInfo.Inheritance.DisableMenuAnimations = xmlnode.GetAttributeAsBool("InheritDisableMenuAnimations");
connectionInfo.Inheritance.DisableCursorShadow = xmlnode.GetAttributeAsBool("InheritDisableCursorShadow"); connectionInfo.Inheritance.DisableCursorShadow = xmlnode.GetAttributeAsBool("InheritDisableCursorShadow");
connectionInfo.Inheritance.DisableCursorBlinking = xmlnode.GetAttributeAsBool("InheritDisableCursorBlinking"); connectionInfo.Inheritance.DisableCursorBlinking = xmlnode.GetAttributeAsBool("InheritDisableCursorBlinking");
connectionInfo.CredentialRecordId = Guid.TryParse(xmlnode.Attributes?["CredentialId"]?.Value, out var credId)
? credId
: Optional<Guid>.Empty;
connectionInfo.Inheritance.CredentialId = xmlnode.GetAttributeAsBool("InheritCredentialId");
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -12,8 +12,7 @@ using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
{ {
public class XmlConnectionsSerializer : ISerializer<ConnectionTreeModel, string>, public class XmlConnectionsSerializer : ISerializer<IConnectionTreeModel,string>, ISerializer<ConnectionInfo, string>
ISerializer<ConnectionInfo, string>
{ {
private readonly ICryptographyProvider _cryptographyProvider; private readonly ICryptographyProvider _cryptographyProvider;
private readonly ISerializer<ConnectionInfo, XElement> _connectionNodeSerializer; private readonly ISerializer<ConnectionInfo, XElement> _connectionNodeSerializer;
@@ -28,7 +27,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
_connectionNodeSerializer = connectionNodeSerializer; _connectionNodeSerializer = connectionNodeSerializer;
} }
public string Serialize(ConnectionTreeModel connectionTreeModel) public string Serialize(IConnectionTreeModel connectionTreeModel)
{ {
var rootNode = (RootNodeInfo)connectionTreeModel.RootNodes.First(node => node is RootNodeInfo); var rootNode = (RootNodeInfo)connectionTreeModel.RootNodes.First(node => node is RootNodeInfo);
return SerializeConnectionsData(rootNode); return SerializeConnectionsData(rootNode);

View File

@@ -9,29 +9,38 @@ namespace mRemoteNG.Config.Serializers.CredentialProviderSerializer
{ {
public class CredentialRepositoryListDeserializer public class CredentialRepositoryListDeserializer
{ {
private readonly ISecureSerializer<IEnumerable<ICredentialRecord>, string> _serializer; public IEnumerable<ICredentialRepository> Deserialize(string xml, IEnumerable<ICredentialRepositoryFactory> factories)
private readonly ISecureDeserializer<string, IEnumerable<ICredentialRecord>> _deserializer;
public CredentialRepositoryListDeserializer(
ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer,
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer)
{ {
if (serializer == null) if (string.IsNullOrEmpty(xml))
throw new ArgumentNullException(nameof(serializer)); return new ICredentialRepository[0];
if (deserializer == null)
throw new ArgumentNullException(nameof(deserializer));
_serializer = serializer;
_deserializer = deserializer;
}
public IEnumerable<ICredentialRepository> Deserialize(string xml)
{
if (string.IsNullOrEmpty(xml)) return new ICredentialRepository[0];
var xdoc = XDocument.Parse(xml); var xdoc = XDocument.Parse(xml);
var repoEntries = xdoc.Descendants("CredentialRepository"); var repoEntries = xdoc.Descendants("CredentialRepository");
var xmlRepoFactory = new XmlCredentialRepositoryFactory(_serializer, _deserializer);
return repoEntries.Select(xmlRepoFactory.Build); return repoEntries
.Select(ParseConfigEntries)
.Select(config =>
factories
.FirstOrDefault(f => string.Equals(f.SupportsConfigType, config.TypeName))?
.Build(config));
}
public ICredentialRepositoryConfig ParseConfigEntries(XElement repositoryXElement)
{
var stringId = repositoryXElement.Attribute("Id")?.Value;
Guid.TryParse(stringId, out var id);
if (id.Equals(Guid.Empty))
id = Guid.NewGuid();
var config = new CredentialRepositoryConfig(id)
{
TypeName = repositoryXElement.Attribute("TypeName")?.Value,
Title = repositoryXElement.Attribute("Title")?.Value,
Source = repositoryXElement.Attribute("Source")?.Value
};
return config;
} }
} }
} }

View File

@@ -0,0 +1,51 @@
using mRemoteNG.Connection;
using mRemoteNG.Credential;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mRemoteNG.Config.Serializers.CredentialSerializer
{
public class CredentialHarvester
{
private readonly IEqualityComparer<ICredentialRecord> _credentialComparer = new CredentialDomainUserPasswordComparer();
/// <summary>
/// Maps a <see cref="ConnectionInfo"/> (by its id) to the <see cref="ICredentialRecord"/>
/// object that was harvested
/// </summary>
/// <param name="harvestConfig"></param>
public ConnectionToCredentialMap Harvest<T>(HarvestConfig<T> harvestConfig)
{
if (harvestConfig == null)
throw new ArgumentNullException(nameof(harvestConfig));
var credentialMap = new ConnectionToCredentialMap();
foreach (var element in harvestConfig.ItemEnumerator())
{
var newCredential = new CredentialRecord
{
Title = harvestConfig.TitleSelector(element),
Username = harvestConfig.UsernameSelector(element),
Domain = harvestConfig.DomainSelector(element),
Password = harvestConfig.PasswordSelector(element)
};
if (!EntryHasSomeCredentialData(newCredential))
continue;
var connectionId = harvestConfig.ConnectionGuidSelector(element);
var existingCredential = credentialMap.Values.FirstOrDefault(record => _credentialComparer.Equals(newCredential, record));
credentialMap.Add(connectionId, existingCredential ?? newCredential);
}
return credentialMap;
}
private bool EntryHasSomeCredentialData(ICredentialRecord e)
{
return !_credentialComparer.Equals(e, new NullCredentialRecord());
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Security;
namespace mRemoteNG.Config.Serializers.CredentialSerializer
{
/// <summary>
/// Configuration for the <see cref="CredentialHarvester"/> to allow it to
/// iterate over and select values from any arbitrary data type. Each element
/// of type <see cref="T"/> represents an object that contains intermixed
/// connection and credential data.
/// </summary>
/// <typeparam name="T"></typeparam>
public class HarvestConfig<T>
{
/// <summary>
/// This will be called to produce a list of all objects
/// that should be iterated over.
/// </summary>
public Func<IEnumerable<T>> ItemEnumerator { get; set; }
/// <summary>
/// Given an item of type <see cref="T"/>, return
/// the <see cref="Guid"/> that represents the connection's unique ID
/// within mRemoteNG.
/// </summary>
public Func<T, Guid> ConnectionGuidSelector { get; set; }
/// <summary>
/// Given an item of type <see cref="T"/>, return a <see cref="string"/>
/// that represents what the associated credential's title should be.
/// </summary>
public Func<T, string> TitleSelector { get; set; }
/// <summary>
/// Given an item of type <see cref="T"/>, return a <see cref="string"/>
/// that represents what the associated credential's username should be.
/// </summary>
public Func<T, string> UsernameSelector { get; set; }
/// <summary>
/// Given an item of type <see cref="T"/>, return a <see cref="string"/>
/// that represents what the associated credential's domain should be.
/// </summary>
public Func<T, string> DomainSelector { get; set; }
/// <summary>
/// Given an item of type <see cref="T"/>, return a <see cref="SecureString"/>
/// that represents what the associated credential's password should be.
/// </summary>
public Func<T, SecureString> PasswordSelector { get; set; }
}
}

View File

@@ -1,59 +1,68 @@
using System; using mRemoteNG.Connection;
using System.IO;
using System.Xml;
using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol;
using mRemoteNG.Container; using mRemoteNG.Container;
using mRemoteNG.Tree; using System;
using mRemoteNG.Tree.Root; using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Xml;
using mRemoteNG.Credential;
using mRemoteNG.Security;
namespace mRemoteNG.Config.Serializers.MiscSerializers namespace mRemoteNG.Config.Serializers.MiscSerializers
{ {
public class PuttyConnectionManagerDeserializer : IDeserializer<string, ConnectionTreeModel> public class PuttyConnectionManagerDeserializer
{ {
public ConnectionTreeModel Deserialize(string puttycmConnectionsXml) public SerializationResult Deserialize(string puttycmConnectionsXml)
{ {
var connectionTreeModel = new ConnectionTreeModel(); var result = new SerializationResult(new List<ConnectionInfo>(), new ConnectionToCredentialMap());
var root = new RootNodeInfo(RootNodeType.Connection);
connectionTreeModel.AddRootNode(root);
var xmlDocument = new XmlDocument(); var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(puttycmConnectionsXml); xmlDocument.LoadXml(puttycmConnectionsXml);
var configurationNode = xmlDocument.SelectSingleNode("/configuration"); var configurationNode = xmlDocument.SelectSingleNode("/configuration");
var rootNodes = configurationNode?.SelectNodes("./root"); var rootXmlNode = configurationNode?.SelectSingleNode("./root");
if (rootNodes == null) return connectionTreeModel; if (rootXmlNode == null)
foreach (XmlNode rootNode in rootNodes) return result;
var rootContainer = ReadContainerProperties(rootXmlNode);
result.ConnectionRecords.Add(rootContainer);
foreach (XmlNode node in rootXmlNode.ChildNodes)
{ {
ImportRootOrContainer(rootNode, root); rootContainer.AddChild(ImportRecursive(node, result.ConnectionToCredentialMap));
} }
return connectionTreeModel; return result;
} }
private void ImportRootOrContainer(XmlNode xmlNode, ContainerInfo parentContainer) private ContainerInfo ImportRecursive(XmlNode xmlNode, ConnectionToCredentialMap credentialMap)
{ {
VerifyNodeType(xmlNode); VerifyNodeType(xmlNode);
var newContainer = ImportContainer(xmlNode, parentContainer); var newContainer = ReadContainerProperties(xmlNode);
var childNodes = xmlNode.SelectNodes("./*"); var childNodes = xmlNode.SelectNodes("./*");
if (childNodes == null) return; if (childNodes == null)
return newContainer;
foreach (XmlNode childNode in childNodes) foreach (XmlNode childNode in childNodes)
{ {
switch (childNode.Name) switch (childNode.Name)
{ {
case "container": case "container":
ImportRootOrContainer(childNode, newContainer); newContainer.AddChild(ImportRecursive(childNode, credentialMap));
break; break;
case "connection": case "connection":
ImportConnection(childNode, newContainer); newContainer.AddChild(ImportConnection(childNode, credentialMap));
break; break;
default: default:
throw (new FileFormatException($"Unrecognized child node ({childNode.Name}).")); throw new FileFormatException($"Unrecognized child node ({childNode.Name}).");
} }
} }
return newContainer;
} }
private void VerifyNodeType(XmlNode xmlNode) private void VerifyNodeType(XmlNode xmlNode)
@@ -82,25 +91,28 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
} }
} }
private ContainerInfo ImportContainer(XmlNode containerNode, ContainerInfo parentContainer) private ContainerInfo ReadContainerProperties(XmlNode containerNode)
{ {
var containerInfo = new ContainerInfo var containerInfo = new ContainerInfo
{ {
Name = containerNode.Attributes?["name"].Value, Name = containerNode.Attributes?["name"].Value,
IsExpanded = bool.Parse(containerNode.Attributes?["expanded"].InnerText ?? "false") IsExpanded = bool.Parse(containerNode.Attributes?["expanded"].InnerText ?? "false")
}; };
parentContainer.AddChild(containerInfo);
return containerInfo; return containerInfo;
} }
private void ImportConnection(XmlNode connectionNode, ContainerInfo parentContainer) private ConnectionInfo ImportConnection(XmlNode connectionNode, ConnectionToCredentialMap credentialMap)
{ {
var connectionNodeType = connectionNode.Attributes?["type"].Value; var connectionNodeType = connectionNode.Attributes?["type"].Value;
if (string.Compare(connectionNodeType, "PuTTY", StringComparison.OrdinalIgnoreCase) != 0) if (string.Compare(connectionNodeType, "PuTTY", StringComparison.OrdinalIgnoreCase) != 0)
throw (new FileFormatException($"Unrecognized connection node type ({connectionNodeType}).")); throw (new FileFormatException($"Unrecognized connection node type ({connectionNodeType})."));
var connectionInfo = ConnectionInfoFromXml(connectionNode); var connectionInfo = ConnectionInfoFromXml(connectionNode);
parentContainer.AddChild(connectionInfo); var cred = CredentialFromXml(connectionNode);
connectionInfo.CredentialRecordId = cred.Id;
credentialMap.Add(Guid.Parse(connectionInfo.ConstantID), cred);
return connectionInfo;
} }
private ConnectionInfo ConnectionInfoFromXml(XmlNode xmlNode) private ConnectionInfo ConnectionInfoFromXml(XmlNode xmlNode)
@@ -129,9 +141,6 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
// ./commandline // ./commandline
connectionInfo.Description = connectionInfoNode.SelectSingleNode("./description")?.InnerText; connectionInfo.Description = connectionInfoNode.SelectSingleNode("./description")?.InnerText;
var loginNode = xmlNode.SelectSingleNode("./login");
connectionInfo.Username = loginNode?.SelectSingleNode("login")?.InnerText;
connectionInfo.Password = loginNode?.SelectSingleNode("password")?.InnerText;
// ./prompt // ./prompt
// ./timeout/connectiontimeout // ./timeout/connectiontimeout
@@ -151,5 +160,19 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
return connectionInfo; return connectionInfo;
} }
private ICredentialRecord CredentialFromXml(XmlNode xmlNode)
{
var loginNode = xmlNode.SelectSingleNode("./login");
var username = loginNode?.SelectSingleNode("login")?.InnerText ?? "";
return new CredentialRecord
{
Title = username,
Username = username,
Domain = "",
Password = loginNode?.SelectSingleNode("password")?.InnerText.ConvertToSecureString() ?? new SecureString()
};
}
} }
} }

View File

@@ -1,21 +1,21 @@
using System; using System;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol.RDP; using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Tree; using System.Collections.Generic;
using mRemoteNG.Tree.Root; using mRemoteNG.Credential;
namespace mRemoteNG.Config.Serializers.MiscSerializers namespace mRemoteNG.Config.Serializers.MiscSerializers
{ {
public class RemoteDesktopConnectionDeserializer : IDeserializer<string, ConnectionTreeModel> public class RemoteDesktopConnectionDeserializer
{ {
// .rdp file schema: https://technet.microsoft.com/en-us/library/ff393699(v=ws.10).aspx // .rdp file schema: https://technet.microsoft.com/en-us/library/ff393699(v=ws.10).aspx
public ConnectionTreeModel Deserialize(string rdcFileContent) public SerializationResult Deserialize(string rdcFileContent)
{ {
var connectionTreeModel = new ConnectionTreeModel();
var root = new RootNodeInfo(RootNodeType.Connection);
connectionTreeModel.AddRootNode(root);
var connectionInfo = new ConnectionInfo(); var connectionInfo = new ConnectionInfo();
var username = "";
var domain = "";
foreach (var line in rdcFileContent.Split(Environment.NewLine.ToCharArray())) foreach (var line in rdcFileContent.Split(Environment.NewLine.ToCharArray()))
{ {
var parts = line.Split(new[] { ':' }, 3); var parts = line.Split(new[] { ':' }, 3);
@@ -24,21 +24,42 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
continue; continue;
} }
var key = parts[0].Trim(); var propertyName = parts[0].Trim().ToLowerInvariant();
var value = parts[2].Trim(); var value = parts[2].Trim();
SetConnectionInfoParameter(connectionInfo, key, value); SetConnectionInfoParameter(connectionInfo, propertyName, value);
if (propertyName.Equals("username"))
username = value;
if (propertyName.Equals("domain"))
domain = value;
} }
root.AddChild(connectionInfo); var serializationResult = new SerializationResult(new List<ConnectionInfo>(), new ConnectionToCredentialMap());
serializationResult.ConnectionRecords.Add(connectionInfo);
return connectionTreeModel; if (username.Length > 0 || domain.Length > 0)
{
var cred = new CredentialRecord
{
Title = domain.Length > 0 ? $"{domain}\\" : "" + username,
Domain = domain,
Username = username
};
serializationResult.ConnectionToCredentialMap.Add(Guid.Parse(connectionInfo.ConstantID), cred);
connectionInfo.CredentialRecordId = cred.Id;
}
return serializationResult;
} }
private void SetConnectionInfoParameter(ConnectionInfo connectionInfo, string key, string value) private void SetConnectionInfoParameter(ConnectionInfo connectionInfo, string key, string value)
{ {
switch (key.ToLower()) switch (key)
{ {
case "full address": case "full address":
var uri = new Uri("dummyscheme" + Uri.SchemeDelimiter + value); var uri = new Uri("dummyscheme" + Uri.SchemeDelimiter + value);
@@ -50,12 +71,6 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
case "server port": case "server port":
connectionInfo.Port = Convert.ToInt32(value); connectionInfo.Port = Convert.ToInt32(value);
break; break;
case "username":
connectionInfo.Username = value;
break;
case "domain":
connectionInfo.Domain = value;
break;
case "session bpp": case "session bpp":
switch (value) switch (value)
{ {

View File

@@ -1,40 +1,44 @@
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Container;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Security;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol; using mRemoteNG.Credential;
using mRemoteNG.Connection.Protocol.RDP; using mRemoteNG.Security;
using mRemoteNG.Container; using mRemoteNG.Tools;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Serializers.MiscSerializers namespace mRemoteNG.Config.Serializers.MiscSerializers
{ {
public class RemoteDesktopConnectionManagerDeserializer : IDeserializer<string, ConnectionTreeModel> public class RemoteDesktopConnectionManagerDeserializer
{ {
private static int _schemaVersion; /* 1 = RDCMan v2.2 // 1 = RDCMan v2.2
3 = RDCMan v2.7 */ // 3 = RDCMan v2.7
private static int _schemaVersion;
public ConnectionTreeModel Deserialize(string rdcmConnectionsXml) public SerializationResult Deserialize(string rdcmConnectionsXml)
{ {
var connectionTreeModel = new ConnectionTreeModel(); var serializationResult = new SerializationResult(new List<ConnectionInfo>(), new ConnectionToCredentialMap());
var root = new RootNodeInfo(RootNodeType.Connection);
var xmlDocument = new XmlDocument(); var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(rdcmConnectionsXml); xmlDocument.LoadXml(rdcmConnectionsXml);
var rdcManNode = xmlDocument.SelectSingleNode("/RDCMan"); var rdcManNode = xmlDocument.SelectSingleNode("/RDCMan");
VerifySchemaVersion(rdcManNode); VerifySchemaVersion(rdcManNode);
VerifyFileVersion(rdcManNode); VerifyFileVersion(rdcManNode);
var fileNode = rdcManNode?.SelectSingleNode("./file"); var fileNode = rdcManNode?.SelectSingleNode("./file");
ImportFileOrGroup(fileNode, root); var importedItem = ImportFileOrGroup(fileNode, serializationResult.ConnectionToCredentialMap);
connectionTreeModel.AddRootNode(root); serializationResult.ConnectionRecords.Add(importedItem);
return connectionTreeModel;
return serializationResult;
} }
private static void VerifySchemaVersion(XmlNode rdcManNode) private static void VerifySchemaVersion(XmlNode rdcManNode)
@@ -79,28 +83,46 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
} }
} }
private static void ImportFileOrGroup(XmlNode xmlNode, ContainerInfo parentContainer) private static ContainerInfo ImportFileOrGroup(XmlNode xmlNode, ConnectionToCredentialMap credentialMap)
{ {
var newContainer = ImportContainer(xmlNode, parentContainer); var newContainer = ImportContainer(xmlNode);
var childNodes = xmlNode.SelectNodes("./group|./server"); var childNodes = xmlNode.SelectNodes("./group|./server");
if (childNodes == null) return; if (childNodes == null)
return newContainer;
foreach (XmlNode childNode in childNodes) foreach (XmlNode childNode in childNodes)
{ {
ConnectionInfo newChild = null;
// ReSharper disable once SwitchStatementMissingSomeCases // ReSharper disable once SwitchStatementMissingSomeCases
switch (childNode.Name) switch (childNode.Name)
{ {
case "group": case "group":
ImportFileOrGroup(childNode, newContainer); newChild = ImportFileOrGroup(childNode, credentialMap);
break; break;
case "server": case "server":
ImportServer(childNode, newContainer); newChild = ConnectionInfoFromXml(childNode);
break; break;
} }
if (newChild == null)
return newContainer;
newContainer.AddChild(newChild);
var cred = ParseCredentials(childNode);
if (!cred.Any())
continue;
newChild.CredentialRecordId = cred.First().Id;
credentialMap.Add(Guid.Parse(newChild.ConstantID), cred.First());
} }
return newContainer;
} }
private static ContainerInfo ImportContainer(XmlNode containerPropertiesNode, ContainerInfo parentContainer) private static ContainerInfo ImportContainer(XmlNode containerPropertiesNode)
{ {
if (_schemaVersion == 1) if (_schemaVersion == 1)
{ {
@@ -120,16 +142,9 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
newContainer.Name = containerPropertiesNode?.SelectSingleNode("./name")?.InnerText ?? Language.NewFolder; newContainer.Name = containerPropertiesNode?.SelectSingleNode("./name")?.InnerText ?? Language.NewFolder;
if (bool.TryParse(containerPropertiesNode?.SelectSingleNode("./expanded")?.InnerText, out var expanded)) if (bool.TryParse(containerPropertiesNode?.SelectSingleNode("./expanded")?.InnerText, out var expanded))
newContainer.IsExpanded = expanded; newContainer.IsExpanded = expanded;
parentContainer.AddChild(newContainer);
return newContainer; return newContainer;
} }
private static void ImportServer(XmlNode serverNode, ContainerInfo parentContainer)
{
var newConnectionInfo = ConnectionInfoFromXml(serverNode);
parentContainer.AddChild(newConnectionInfo);
}
private static ConnectionInfo ConnectionInfoFromXml(XmlNode xmlNode) private static ConnectionInfo ConnectionInfoFromXml(XmlNode xmlNode)
{ {
var connectionInfo = new ConnectionInfo {Protocol = ProtocolType.RDP}; var connectionInfo = new ConnectionInfo {Protocol = ProtocolType.RDP};
@@ -152,25 +167,7 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
connectionInfo.Description = propertiesNode?.SelectSingleNode("./comment")?.InnerText ?? string.Empty; connectionInfo.Description = propertiesNode?.SelectSingleNode("./comment")?.InnerText ?? string.Empty;
var logonCredentialsNode = xmlNode.SelectSingleNode("./logonCredentials"); var logonCredentialsNode = xmlNode.SelectSingleNode("./logonCredentials");
if (logonCredentialsNode?.Attributes?["inherit"]?.Value == "None") if (logonCredentialsNode?.Attributes?["inherit"]?.Value != "None")
{
connectionInfo.Username = logonCredentialsNode.SelectSingleNode("userName")?.InnerText ?? string.Empty;
var passwordNode = logonCredentialsNode.SelectSingleNode("./password");
if (_schemaVersion == 1) // Version 2.2 allows clear text passwords
{
connectionInfo.Password = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
? passwordNode.InnerText
: DecryptRdcManPassword(passwordNode?.InnerText);
}
else
{
connectionInfo.Password = DecryptRdcManPassword(passwordNode?.InnerText);
}
connectionInfo.Domain = logonCredentialsNode.SelectSingleNode("./domain")?.InnerText ?? string.Empty;
}
else
{ {
connectionInfo.Inheritance.Username = true; connectionInfo.Inheritance.Username = true;
connectionInfo.Inheritance.Password = true; connectionInfo.Inheritance.Password = true;
@@ -206,7 +203,7 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
var passwordNode = gatewaySettingsNode.SelectSingleNode("./password"); var passwordNode = gatewaySettingsNode.SelectSingleNode("./password");
connectionInfo.RDGatewayPassword = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True" connectionInfo.RDGatewayPassword = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
? passwordNode.InnerText ? passwordNode.InnerText
: DecryptRdcManPassword(passwordNode?.InnerText); : DecryptRdcManPassword(passwordNode?.InnerText).ConvertToUnsecureString();
connectionInfo.RDGatewayDomain = gatewaySettingsNode.SelectSingleNode("./domain")?.InnerText ?? string.Empty; connectionInfo.RDGatewayDomain = gatewaySettingsNode.SelectSingleNode("./domain")?.InnerText ?? string.Empty;
// ./logonMethod // ./logonMethod
@@ -348,22 +345,45 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
return connectionInfo; return connectionInfo;
} }
private static string DecryptRdcManPassword(string ciphertext) private static Optional<ICredentialRecord> ParseCredentials(XmlNode xmlNode)
{
var logonCredentialsNode = xmlNode.SelectSingleNode("./logonCredentials");
if (logonCredentialsNode?.Attributes?["inherit"]?.Value != "None")
return Optional<ICredentialRecord>.Empty;
var username = logonCredentialsNode.SelectSingleNode("userName")?.InnerText ?? "";
var domain = logonCredentialsNode.SelectSingleNode("./domain")?.InnerText ?? "";
var passwordNode = logonCredentialsNode.SelectSingleNode("./password");
var creds = new CredentialRecord
{
Title = domain.Length > 0 ? $"{domain}\\{username}" : $"{username}",
Username = username,
Domain = domain,
Password = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
? passwordNode.InnerText.ConvertToSecureString()
: DecryptRdcManPassword(passwordNode?.InnerText)
};
return creds;
}
private static SecureString DecryptRdcManPassword(string ciphertext)
{ {
if (string.IsNullOrEmpty(ciphertext)) if (string.IsNullOrEmpty(ciphertext))
return string.Empty; return new SecureString();
try try
{ {
var plaintextData = ProtectedData.Unprotect(Convert.FromBase64String(ciphertext), new byte[] { }, var plaintextData = ProtectedData.Unprotect(Convert.FromBase64String(ciphertext), new byte[] { },
DataProtectionScope.LocalMachine); DataProtectionScope.LocalMachine);
var charArray = Encoding.Unicode.GetChars(plaintextData); return Encoding.Unicode.GetString(plaintextData).ConvertToSecureString();
return new string(charArray);
} }
catch (Exception /*ex*/) catch (Exception /*ex*/)
{ {
//Runtime.MessageCollector.AddExceptionMessage("RemoteDesktopConnectionManager.DecryptPassword() failed.", ex, logOnly: true); //Runtime.MessageCollector.AddExceptionMessage("RemoteDesktopConnectionManager.DecryptPassword() failed.", ex, logOnly: true);
return string.Empty; return new SecureString();
} }
} }
} }

View File

@@ -0,0 +1,23 @@
using mRemoteNG.Connection;
using mRemoteNG.Tools;
using System.Collections.Generic;
namespace mRemoteNG.Config.Serializers
{
/// <summary>
/// Represents the connections and credentials found during a deserialization.
/// </summary>
public class SerializationResult
{
public List<ConnectionInfo> ConnectionRecords { get; }
public ConnectionToCredentialMap ConnectionToCredentialMap { get; }
public SerializationResult(
List<ConnectionInfo> connectionRecords,
ConnectionToCredentialMap connectionToCredentialMap)
{
ConnectionRecords = connectionRecords.ThrowIfNull(nameof(connectionRecords));
ConnectionToCredentialMap = connectionToCredentialMap.ThrowIfNull(nameof(connectionToCredentialMap));
}
}
}

View File

@@ -0,0 +1,109 @@
using mRemoteNG.App;
using mRemoteNG.Config.Serializers.CredentialSerializer;
using mRemoteNG.Connection;
using mRemoteNG.Credential;
using mRemoteNG.Security;
using mRemoteNG.Security.Authentication;
using mRemoteNG.Security.Factories;
using mRemoteNG.Tools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
namespace mRemoteNG.Config.Serializers.Versioning
{
public class XmlCredentialManagerUpgrader
{
private readonly XmlConnectionsDeserializer _deserializer;
public XmlCredentialManagerUpgrader(XmlConnectionsDeserializer decoratedDeserializer)
{
_deserializer = decoratedDeserializer.ThrowIfNull(nameof(decoratedDeserializer));
}
public SerializationResult Deserialize(string serializedData, ConnectionToCredentialMap upgradeMap)
{
var serializedDataAsXDoc = EnsureConnectionXmlElementsHaveIds(serializedData);
var serializedDataWithIds = $"{serializedDataAsXDoc.Declaration}{serializedDataAsXDoc}";
var serializationResult = _deserializer.Deserialize(serializedDataWithIds);
if (upgradeMap != null)
ApplyCredentialMapping(upgradeMap, serializationResult.ConnectionRecords.FlattenConnectionTree());
return serializationResult;
}
private XDocument EnsureConnectionXmlElementsHaveIds(string serializedData)
{
var xdoc = XDocument.Parse(serializedData);
xdoc.Declaration = new XDeclaration("1.0", "utf-8", null);
var adapter = new ConfConsEnsureConnectionsHaveIds();
adapter.EnsureElementsHaveIds(xdoc);
return xdoc;
}
public ConnectionToCredentialMap UpgradeUserFilesForCredentialManager(XDocument xdoc)
{
if (!CredentialManagerUpgradeNeeded(xdoc))
{
return null;
}
var cryptoProvider = new CryptoProviderFactoryFromXml(xdoc.Root).Build();
var encryptedValue = xdoc.Root?.Attribute("Protected")?.Value;
var auth = new PasswordAuthenticator(cryptoProvider, encryptedValue, () => MiscTools.PasswordDialog("", false));
if (!auth.Authenticate(Runtime.EncryptionKey))
throw new Exception("Could not authenticate");
var keyForOldConnectionFile = auth.LastAuthenticatedPassword;
var preCredManagerXmlHarvestConfig = new HarvestConfig<XElement>
{
ItemEnumerator = () => xdoc.Descendants("Node"),
ConnectionGuidSelector = e =>
{
Guid.TryParse(e.Attribute("Id")?.Value, out var connectionId);
return connectionId;
},
UsernameSelector = e => e.Attribute("Username")?.Value,
DomainSelector = e => e.Attribute("Domain")?.Value,
PasswordSelector = e => cryptoProvider.Decrypt(e.Attribute("Password")?.Value, keyForOldConnectionFile).ConvertToSecureString(),
TitleSelector = e => $"{e.Attribute("Domain")?.Value}{(string.IsNullOrEmpty(e.Attribute("Domain")?.Value) ? "" : "\\")}{e.Attribute("Username")?.Value}"
};
var credentialHarvester = new CredentialHarvester();
var harvestedCredentials = credentialHarvester.Harvest(preCredManagerXmlHarvestConfig);
return harvestedCredentials;
}
/// <summary>
/// If any connections in the xml contain a Username/Domain/Password field, we need to upgrade
/// it to be compatible with the credential manager.
/// </summary>
/// <param name="xdoc"></param>
public static bool CredentialManagerUpgradeNeeded(XContainer xdoc)
{
return xdoc
.Descendants("Node")
.Any(n =>
n.Attribute("Username") != null ||
n.Attribute("Domain") != null ||
n.Attribute("Password") != null);
}
private void ApplyCredentialMapping(IDictionary<Guid, ICredentialRecord> map, IEnumerable<AbstractConnectionRecord> connectionRecords)
{
foreach (var connectionInfo in connectionRecords)
{
Guid.TryParse(connectionInfo.ConstantID, out var id);
if (map.ContainsKey(id))
connectionInfo.CredentialRecordId = map[id].Id.ToOptional();
}
}
}
}

View File

@@ -1,12 +1,18 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Drawing.Design;
using System.Linq;
using mRemoteNG.App;
using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.Http; using mRemoteNG.Connection.Protocol.Http;
using mRemoteNG.Connection.Protocol.RDP; using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Connection.Protocol.VNC; using mRemoteNG.Connection.Protocol.VNC;
using mRemoteNG.Credential;
using mRemoteNG.Properties; using mRemoteNG.Properties;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using mRemoteNG.Tools.Attributes; using mRemoteNG.Tools.Attributes;
using mRemoteNG.UI.Controls.Adapters;
namespace mRemoteNG.Connection namespace mRemoteNG.Connection
@@ -21,6 +27,7 @@ namespace mRemoteNG.Connection
private string _panel; private string _panel;
private string _hostname; private string _hostname;
private Optional<Guid> _credentialRecordId = new Optional<Guid>();
private string _username = ""; private string _username = "";
private string _password = ""; private string _password = "";
private string _domain = ""; private string _domain = "";
@@ -159,6 +166,7 @@ namespace mRemoteNG.Connection
set => SetField(ref _port, value, "Port"); set => SetField(ref _port, value, "Port");
} }
[Browsable(false)]
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2), [LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Username)), LocalizedAttributes.LocalizedDisplayName(nameof(Language.Username)),
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionUsername)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionUsername)),
@@ -169,6 +177,7 @@ namespace mRemoteNG.Connection
set => SetField(ref _username, Settings.Default.DoNotTrimUsername ? value : value?.Trim(), "Username"); set => SetField(ref _username, Settings.Default.DoNotTrimUsername ? value : value?.Trim(), "Username");
} }
[Browsable(false)]
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2), [LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Password)), LocalizedAttributes.LocalizedDisplayName(nameof(Language.Password)),
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionPassword)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionPassword)),
@@ -180,6 +189,7 @@ namespace mRemoteNG.Connection
set => SetField(ref _password, value, "Password"); set => SetField(ref _password, value, "Password");
} }
[Browsable(false)]
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2), [LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Domain)), LocalizedAttributes.LocalizedDisplayName(nameof(Language.Domain)),
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionDomain)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionDomain)),
@@ -210,6 +220,26 @@ namespace mRemoteNG.Connection
get => GetPropertyValue("SSHTunnelConnectionName", _sshTunnelConnectionName).Trim(); get => GetPropertyValue("SSHTunnelConnectionName", _sshTunnelConnectionName).Trim();
set => SetField(ref _sshTunnelConnectionName, value?.Trim(), "SSHTunnelConnectionName"); set => SetField(ref _sshTunnelConnectionName, value?.Trim(), "SSHTunnelConnectionName");
} }
[Browsable(false)]
public virtual Optional<Guid> CredentialRecordId
{
get => GetPropertyValue(nameof(CredentialRecordId), _credentialRecordId);
set => SetField(ref _credentialRecordId, value ?? Optional<Guid>.Empty, nameof(CredentialRecordId));
}
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Credentials)),
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionCredentials)),
AttributeUsedInAllProtocolsExcept()]
[Editor(typeof(CredentialRecordListAdaptor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public virtual ICredentialRecord CredentialRecord
{
// TODO: this static ref to the cred service makes testing difficult. refactor it to allow easy mocking
get => Runtime.CredentialService.GetEffectiveCredentialRecord(CredentialRecordId, false).FirstOrDefault();
set => CredentialRecordId = Optional<Guid>.FromNullable(value?.Id);
}
#endregion #endregion
#region Protocol #region Protocol

View File

@@ -135,22 +135,24 @@ namespace mRemoteNG.Connection
return filteredProperties; return filteredProperties;
} }
public virtual IEnumerable<PropertyInfo> GetSerializableProperties() public virtual IEnumerable<PropertyInfo> GetSerializableProperties()
{ {
var excludedProperties = new[] var excludedProperties = new[] {
{ nameof(Parent), nameof(Name), nameof(Hostname), nameof(Port),
"Parent", "Name", "Hostname", "Port", "Inheritance", "OpenConnections", nameof(Username), nameof(Domain), nameof(Password),
"IsContainer", "IsDefault", "PositionID", "ConstantID", "TreeNode", "IsQuickConnect", "PleaseConnect" nameof(Inheritance), nameof(OpenConnections),
}; nameof(IsContainer), nameof(IsDefault), nameof(ConstantID),
nameof(IsQuickConnect), nameof(PleaseConnect), nameof(CredentialRecord)
};
return GetProperties(excludedProperties); return GetProperties(excludedProperties);
} }
public virtual void SetParent(ContainerInfo newParent) public virtual void SetParent(ContainerInfo newParent)
{ {
RemoveParent(); RemoveParent();
newParent?.AddChild(this); newParent?.AddChild(this);
} }
public void RemoveParent() public void RemoveParent()
{ {

View File

@@ -48,7 +48,13 @@ namespace mRemoteNG.Connection
#endregion #endregion
#region Connection #region
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 3),
LocalizedAttributes.LocalizedDisplayNameInherit(nameof(Language.Credentials)),
LocalizedAttributes.LocalizedDescriptionInherit(nameof(Language.PropertyDescriptionCredentials)),
TypeConverter(typeof(MiscTools.YesNoTypeConverter))]
public bool CredentialId { get; set; }
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 3), [LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 3),
LocalizedAttributes.LocalizedDisplayNameInherit(nameof(Language.Username)), LocalizedAttributes.LocalizedDisplayNameInherit(nameof(Language.Username)),

View File

@@ -8,6 +8,7 @@ using mRemoteNG.Messages;
using mRemoteNG.Properties; using mRemoteNG.Properties;
using mRemoteNG.UI.Forms; using mRemoteNG.UI.Forms;
using mRemoteNG.UI.Panels; using mRemoteNG.UI.Panels;
using mRemoteNG.Credential;
using mRemoteNG.UI.Tabs; using mRemoteNG.UI.Tabs;
using mRemoteNG.UI.Window; using mRemoteNG.UI.Window;
using WeifenLuo.WinFormsUI.Docking; using WeifenLuo.WinFormsUI.Docking;
@@ -19,6 +20,12 @@ namespace mRemoteNG.Connection
{ {
private readonly PanelAdder _panelAdder = new PanelAdder(); private readonly PanelAdder _panelAdder = new PanelAdder();
private readonly List<string> _activeConnections = new List<string>(); private readonly List<string> _activeConnections = new List<string>();
private readonly CredentialService _credentialService;
public ConnectionInitiator(CredentialService credentialService)
{
_credentialService = credentialService;
}
public IEnumerable<string> ActiveConnections => _activeConnections; public IEnumerable<string> ActiveConnections => _activeConnections;

View File

@@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using mRemoteNG.App; using mRemoteNG.App;
@@ -11,6 +13,7 @@ using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Putty; using mRemoteNG.Config.Putty;
using mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql; using mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql;
using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol;
using mRemoteNG.Credential;
using mRemoteNG.Messages; using mRemoteNG.Messages;
using mRemoteNG.Properties; using mRemoteNG.Properties;
using mRemoteNG.Security; using mRemoteNG.Security;
@@ -21,7 +24,7 @@ using mRemoteNG.UI;
namespace mRemoteNG.Connection namespace mRemoteNG.Connection
{ {
public class ConnectionsService public class ConnectionsService : IConnectionsService
{ {
private static readonly object SaveLock = new object(); private static readonly object SaveLock = new object();
private readonly PuttySessionsManager _puttySessionsManager; private readonly PuttySessionsManager _puttySessionsManager;
@@ -30,6 +33,7 @@ namespace mRemoteNG.Connection
private bool _batchingSaves = false; private bool _batchingSaves = false;
private bool _saveRequested = false; private bool _saveRequested = false;
private bool _saveAsyncRequested = false; private bool _saveAsyncRequested = false;
private readonly ICredentialService _credentialService;
public bool IsConnectionsFileLoaded { get; set; } public bool IsConnectionsFileLoaded { get; set; }
public bool UsingDatabase { get; private set; } public bool UsingDatabase { get; private set; }
@@ -37,18 +41,18 @@ namespace mRemoteNG.Connection
public RemoteConnectionsSyncronizer RemoteConnectionsSyncronizer { get; set; } public RemoteConnectionsSyncronizer RemoteConnectionsSyncronizer { get; set; }
public DateTime LastSqlUpdate { get; set; } public DateTime LastSqlUpdate { get; set; }
public ConnectionTreeModel ConnectionTreeModel { get; private set; } public IConnectionTreeModel ConnectionTreeModel { get; private set; } = new ConnectionTreeModel();
public ConnectionsService(PuttySessionsManager puttySessionsManager) public ConnectionsService(PuttySessionsManager puttySessionsManager, ICredentialService credentialService)
{ {
if (puttySessionsManager == null) _puttySessionsManager = puttySessionsManager.ThrowIfNull(nameof(puttySessionsManager));
throw new ArgumentNullException(nameof(puttySessionsManager)); _credentialService = credentialService.ThrowIfNull(nameof(credentialService));
_puttySessionsManager = puttySessionsManager;
var path = SettingsFileInfo.SettingsPath; var path = SettingsFileInfo.SettingsPath;
_localConnectionPropertiesDataProvider = _localConnectionPropertiesDataProvider =
new FileDataProvider(Path.Combine(path, "LocalConnectionProperties.xml")); new FileDataProvider(Path.Combine(path, "LocalConnectionProperties.xml"));
_localConnectionPropertiesSerializer = new LocalConnectionPropertiesXmlSerializer(); _localConnectionPropertiesSerializer = new LocalConnectionPropertiesXmlSerializer();
_puttySessionsManager.RootPuttySessionsNodes.ForEach(node => ConnectionTreeModel.AddRootNode(node));
} }
public void NewConnectionsFile(string filename) public void NewConnectionsFile(string filename)
@@ -56,10 +60,9 @@ namespace mRemoteNG.Connection
try try
{ {
filename.ThrowIfNullOrEmpty(nameof(filename)); filename.ThrowIfNullOrEmpty(nameof(filename));
var newConnectionsModel = new ConnectionTreeModel(); ConnectionTreeModel.AddRootNode(new RootNodeInfo(RootNodeType.Connection));
newConnectionsModel.AddRootNode(new RootNodeInfo(RootNodeType.Connection)); SaveConnections(ConnectionTreeModel, false, new SaveFilter(), filename, true);
SaveConnections(newConnectionsModel, false, new SaveFilter(), filename, true); LoadConnections(false, filename);
LoadConnections(false, false, filename);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -123,22 +126,21 @@ namespace mRemoteNG.Connection
/// <param name="useDatabase"></param> /// <param name="useDatabase"></param>
/// <param name="import"></param> /// <param name="import"></param>
/// <param name="connectionFileName"></param> /// <param name="connectionFileName"></param>
public void LoadConnections(bool useDatabase, bool import, string connectionFileName) public void LoadConnections(bool useDatabase, string connectionFileName)
{ {
var oldConnectionTreeModel = ConnectionTreeModel;
var oldIsUsingDatabaseValue = UsingDatabase; var oldIsUsingDatabaseValue = UsingDatabase;
var connectionLoader = useDatabase var connectionLoader = useDatabase
? (IConnectionsLoader)new SqlConnectionsLoader(_localConnectionPropertiesSerializer, ? (IConnectionsLoader)new SqlConnectionsLoader(_localConnectionPropertiesSerializer,
_localConnectionPropertiesDataProvider) _localConnectionPropertiesDataProvider)
: new XmlConnectionsLoader(connectionFileName); : new XmlConnectionsLoader(connectionFileName, _credentialService, this);
var newConnectionTreeModel = connectionLoader.Load(); var serializationResult = connectionLoader.Load();
if (useDatabase) if (useDatabase)
LastSqlUpdate = DateTime.Now; LastSqlUpdate = DateTime.Now;
if (newConnectionTreeModel == null) if (serializationResult == null)
{ {
DialogFactory.ShowLoadConnectionsFailedDialog(connectionFileName, "Decrypting connection file failed", DialogFactory.ShowLoadConnectionsFailedDialog(connectionFileName, "Decrypting connection file failed",
IsConnectionsFileLoaded); IsConnectionsFileLoaded);
@@ -149,16 +151,17 @@ namespace mRemoteNG.Connection
ConnectionFileName = connectionFileName; ConnectionFileName = connectionFileName;
UsingDatabase = useDatabase; UsingDatabase = useDatabase;
if (!import) if (ConnectionTreeModel.RootNodes.Any())
{ ConnectionTreeModel.RemoveRootNode(ConnectionTreeModel.RootNodes.First());
_puttySessionsManager.AddSessions();
newConnectionTreeModel.RootNodes.AddRange(_puttySessionsManager.RootPuttySessionsNodes); var rootNode = new RootNodeInfo(RootNodeType.Connection);
} rootNode.AddChildRange(serializationResult.ConnectionRecords);
ConnectionTreeModel.AddRootNode(rootNode);
ConnectionTreeModel = newConnectionTreeModel;
UpdateCustomConsPathSetting(connectionFileName); UpdateCustomConsPathSetting(connectionFileName);
RaiseConnectionsLoadedEvent(oldConnectionTreeModel, newConnectionTreeModel, oldIsUsingDatabaseValue, // TODO: fix this call
useDatabase, connectionFileName); RaiseConnectionsLoadedEvent(new List<ConnectionInfo>(), new List<ConnectionInfo>(),
oldIsUsingDatabaseValue, useDatabase, connectionFileName);
Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg,
$"Connections loaded using {connectionLoader.GetType().Name}"); $"Connections loaded using {connectionLoader.GetType().Name}");
} }
@@ -224,12 +227,13 @@ namespace mRemoteNG.Connection
/// Optional. The name of the property that triggered /// Optional. The name of the property that triggered
/// this save. /// this save.
/// </param> /// </param>
public void SaveConnections(ConnectionTreeModel connectionTreeModel, public void SaveConnections(
bool useDatabase, IConnectionTreeModel connectionTreeModel,
SaveFilter saveFilter, bool useDatabase,
string connectionFileName, SaveFilter saveFilter,
bool forceSave = false, string connectionFileName,
string propertyNameTrigger = "") bool forceSave = false,
string propertyNameTrigger = "")
{ {
if (connectionTreeModel == null) if (connectionTreeModel == null)
return; return;
@@ -251,9 +255,8 @@ namespace mRemoteNG.Connection
var previouslyUsingDatabase = UsingDatabase; var previouslyUsingDatabase = UsingDatabase;
var saver = useDatabase var saver = useDatabase
? (ISaver<ConnectionTreeModel>)new SqlConnectionsSaver(saveFilter, ? (ISaver<IConnectionTreeModel>)new SqlConnectionsSaver(saveFilter, _localConnectionPropertiesSerializer,
_localConnectionPropertiesSerializer, _localConnectionPropertiesDataProvider)
_localConnectionPropertiesDataProvider)
: new XmlConnectionsSaver(connectionFileName, saveFilter); : new XmlConnectionsSaver(connectionFileName, saveFilter);
saver.Save(connectionTreeModel, propertyNameTrigger); saver.Save(connectionTreeModel, propertyNameTrigger);
@@ -356,31 +359,22 @@ namespace mRemoteNG.Connection
public event EventHandler<ConnectionsLoadedEventArgs> ConnectionsLoaded; public event EventHandler<ConnectionsLoadedEventArgs> ConnectionsLoaded;
public event EventHandler<ConnectionsSavedEventArgs> ConnectionsSaved; public event EventHandler<ConnectionsSavedEventArgs> ConnectionsSaved;
private void RaiseConnectionsLoadedEvent(Optional<ConnectionTreeModel> previousTreeModel, private void RaiseConnectionsLoadedEvent(List<ConnectionInfo> removedConnections, List<ConnectionInfo> addedConnections,
ConnectionTreeModel newTreeModel, bool previousSourceWasDatabase, bool newSourceIsDatabase,
bool previousSourceWasDatabase, string newSourcePath)
bool newSourceIsDatabase,
string newSourcePath)
{ {
ConnectionsLoaded?.Invoke(this, new ConnectionsLoadedEventArgs( ConnectionsLoaded?.Invoke(this, new ConnectionsLoadedEventArgs(
previousTreeModel, removedConnections,
newTreeModel, addedConnections,
previousSourceWasDatabase, previousSourceWasDatabase,
newSourceIsDatabase, newSourceIsDatabase,
newSourcePath)); newSourcePath));
} }
private void RaiseConnectionsSavedEvent(ConnectionTreeModel modelThatWasSaved, private void RaiseConnectionsSavedEvent(IConnectionTreeModel modelThatWasSaved, bool previouslyUsingDatabase, bool usingDatabase, string connectionFileName)
bool previouslyUsingDatabase,
bool usingDatabase,
string connectionFileName)
{ {
ConnectionsSaved?.Invoke(this, ConnectionsSaved?.Invoke(this, new ConnectionsSavedEventArgs(modelThatWasSaved, previouslyUsingDatabase, usingDatabase, connectionFileName));
new ConnectionsSavedEventArgs(modelThatWasSaved, previouslyUsingDatabase,
usingDatabase,
connectionFileName));
} }
#endregion #endregion
} }
} }

View File

@@ -67,8 +67,11 @@ namespace mRemoteNG.Connection
throw new SettingsPropertyNotFoundException($"No property with name '{expectedPropertyName}' found."); throw new SettingsPropertyNotFoundException($"No property with name '{expectedPropertyName}' found.");
// ensure value is of correct type // ensure value is of correct type
var value = Convert.ChangeType(property.GetValue(Instance, null), var value = property.PropertyType == propertyFromDestination.PropertyType
propertyFromDestination.PropertyType); ? property.GetValue(Instance, null)
: propertyFromDestination.PropertyType == typeof(string)
? property.GetValue(Instance, null).ToString()
: Convert.ChangeType(property.GetValue(Instance, null), propertyFromDestination.PropertyType);
propertyFromDestination.SetValue(destinationInstance, value, null); propertyFromDestination.SetValue(destinationInstance, value, null);
} }

View File

@@ -0,0 +1,78 @@
using System;
using mRemoteNG.Config.Connections;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Security;
using mRemoteNG.Tree;
namespace mRemoteNG.Connection
{
public interface IConnectionsService
{
IConnectionTreeModel ConnectionTreeModel { get; }
void NewConnectionsFile(string filename);
ConnectionInfo CreateQuickConnect(string connectionString, ProtocolType protocol);
/// <summary>
/// Load connections from a source. <see cref="connectionFileName"/> is ignored if
/// <see cref="useDatabase"/> is true.
/// </summary>
/// <param name="useDatabase"></param>
/// <param name="import"></param>
/// <param name="connectionFileName"></param>
void LoadConnections(bool useDatabase, string connectionFileName);
/// <summary>
/// When turned on, calls to <see cref="ConnectionsService.SaveConnections()"/> or
/// <see cref="ConnectionsService.SaveConnectionsAsync"/> will not immediately execute.
/// Instead, they will be deferred until <see cref="ConnectionsService.EndBatchingSaves"/>
/// is called.
/// </summary>
void BeginBatchingSaves();
/// <summary>
/// Immediately executes a single <see cref="ConnectionsService.SaveConnections()"/> or
/// <see cref="ConnectionsService.SaveConnectionsAsync"/> if one has been requested
/// since calling <see cref="ConnectionsService.BeginBatchingSaves"/>.
/// </summary>
void EndBatchingSaves();
/// <summary>
/// Saves the currently loaded <see cref="ConnectionsService.ConnectionTreeModel"/> with
/// no <see cref="SaveFilter"/>.
/// </summary>
void SaveConnections();
/// <summary>
/// Saves the given <see cref="ConnectionsService.ConnectionTreeModel"/>.
/// If <see cref="useDatabase"/> is true, <see cref="connectionFileName"/> is ignored
/// </summary>
/// <param name="connectionTreeModel"></param>
/// <param name="useDatabase"></param>
/// <param name="saveFilter"></param>
/// <param name="connectionFileName"></param>
/// <param name="forceSave">Bypasses safety checks that prevent saving if a connection file isn't loaded.</param>
/// <param name="propertyNameTrigger">
/// Optional. The name of the property that triggered
/// this save.
/// </param>
void SaveConnections(
IConnectionTreeModel connectionTreeModel,
bool useDatabase,
SaveFilter saveFilter,
string connectionFileName,
bool forceSave = false,
string propertyNameTrigger = "");
/// <summary>
/// Save the currently loaded connections asynchronously
/// </summary>
/// <param name="propertyNameTrigger">
/// Optional. The name of the property that triggered
/// this save.
/// </param>
void SaveConnectionsAsync(string propertyNameTrigger = "");
event EventHandler<ConnectionsLoadedEventArgs> ConnectionsLoaded;
event EventHandler<ConnectionsSavedEventArgs> ConnectionsSaved;
}
}

View File

@@ -63,7 +63,7 @@ namespace mRemoteNG.Connection.Protocol
return false; return false;
} }
var argParser = new ExternalToolArgumentParser(_externalTool.ConnectionInfo); var argParser = new ExternalToolArgumentParser(_externalTool.ConnectionInfo, Runtime.CredentialService);
_process = new Process _process = new Process
{ {
StartInfo = StartInfo =

View File

@@ -1,15 +1,15 @@
using mRemoteNG.App; using mRemoteNG.App;
using mRemoteNG.Messages; using mRemoteNG.Messages;
using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using mRemoteNG.Tools.Cmdline; using mRemoteNG.Tools.Cmdline;
using mRemoteNG.UI; using mRemoteNG.UI;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using mRemoteNG.Properties; using mRemoteNG.Security;
// ReSharper disable ArrangeAccessorOwnerBody // ReSharper disable ArrangeAccessorOwnerBody
@@ -78,45 +78,20 @@ namespace mRemoteNG.Connection.Protocol
if (PuttyProtocol == Putty_Protocol.ssh) if (PuttyProtocol == Putty_Protocol.ssh)
{ {
var username = "";
var password = "";
if (!string.IsNullOrEmpty(InterfaceControl.Info?.Username))
{
username = InterfaceControl.Info.Username;
}
else
{
// ReSharper disable once SwitchStatementMissingSomeCases
switch (Settings.Default.EmptyCredentials)
{
case "windows":
username = Environment.UserName;
break;
case "custom":
username = Settings.Default.DefaultUsername;
break;
}
}
if (!string.IsNullOrEmpty(InterfaceControl.Info?.Password))
{
password = InterfaceControl.Info.Password;
}
else
{
if (Settings.Default.EmptyCredentials == "custom")
{
var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
password = cryptographyProvider.Decrypt(Settings.Default.DefaultPassword,
Runtime.EncryptionKey);
}
}
arguments.Add("-" + (int)PuttySSHVersion); arguments.Add("-" + (int)PuttySSHVersion);
if (!Force.HasFlag(ConnectionInfo.Force.NoCredentials)) if (!Force.HasFlag(ConnectionInfo.Force.NoCredentials))
{ {
var cred = Runtime.CredentialService.GetEffectiveCredentialRecord(InterfaceControl?.Info.CredentialRecordId
.FirstOrDefault()).FirstOrDefault();
var username = cred.Username;
var password = cred.Password.ConvertToUnsecureString();
if (!string.IsNullOrEmpty(username)) if (!string.IsNullOrEmpty(username))
{ {
arguments.Add("-l", username); arguments.Add("-l", username);
@@ -149,11 +124,11 @@ namespace mRemoteNG.Connection.Protocol
PuttyProcess.Exited += ProcessExited; PuttyProcess.Exited += ProcessExited;
PuttyProcess.Start(); PuttyProcess.Start();
PuttyProcess.WaitForInputIdle(Settings.Default.MaxPuttyWaitTime * 1000); PuttyProcess.WaitForInputIdle(Properties.Settings.Default.MaxPuttyWaitTime * 1000);
var startTicks = Environment.TickCount; var startTicks = Environment.TickCount;
while (PuttyHandle.ToInt32() == 0 & while (PuttyHandle.ToInt32() == 0 &
Environment.TickCount < startTicks + Settings.Default.MaxPuttyWaitTime * 1000) Environment.TickCount < startTicks + Properties.Settings.Default.MaxPuttyWaitTime * 1000)
{ {
if (_isPuttyNg) if (_isPuttyNg)
{ {

View File

@@ -8,12 +8,13 @@ using AxMSTSCLib;
using mRemoteNG.App; using mRemoteNG.App;
using mRemoteNG.Messages; using mRemoteNG.Messages;
using mRemoteNG.Properties; using mRemoteNG.Properties;
using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using mRemoteNG.UI; using mRemoteNG.UI;
using mRemoteNG.UI.Forms; using mRemoteNG.UI.Forms;
using mRemoteNG.UI.Tabs; using mRemoteNG.UI.Tabs;
using MSTSCLib; using MSTSCLib;
using System.Linq;
using mRemoteNG.Security;
namespace mRemoteNG.Connection.Protocol.RDP namespace mRemoteNG.Connection.Protocol.RDP
{ {
@@ -378,9 +379,9 @@ namespace mRemoteNG.Connection.Protocol.RDP
{ {
if (connectionInfo.RDGatewayUseConnectionCredentials == RDGatewayUseConnectionCredentials.Yes) if (connectionInfo.RDGatewayUseConnectionCredentials == RDGatewayUseConnectionCredentials.Yes)
{ {
_rdpClient.TransportSettings2.GatewayUsername = connectionInfo.Username; _rdpClient.TransportSettings2.GatewayUsername = connectionInfo.CredentialRecord.Username;
_rdpClient.TransportSettings2.GatewayPassword = connectionInfo.Password; _rdpClient.TransportSettings2.GatewayPassword = connectionInfo.CredentialRecord.Password.ConvertToUnsecureString();
_rdpClient.TransportSettings2.GatewayDomain = connectionInfo?.Domain; _rdpClient.TransportSettings2.GatewayDomain = connectionInfo.CredentialRecord.Domain;
} }
else if (connectionInfo.RDGatewayUseConnectionCredentials == else if (connectionInfo.RDGatewayUseConnectionCredentials ==
RDGatewayUseConnectionCredentials.SmartCard) RDGatewayUseConnectionCredentials.SmartCard)
@@ -456,58 +457,15 @@ namespace mRemoteNG.Connection.Protocol.RDP
return; return;
} }
var userName = connectionInfo?.Username ?? ""; var cred = Runtime.CredentialService.GetEffectiveCredentialRecord(connectionInfo.CredentialRecordId
var password = connectionInfo?.Password ?? "";
var domain = connectionInfo?.Domain ?? "";
if (string.IsNullOrEmpty(userName)) .FirstOrDefault()).FirstOrDefault();
{
if (Settings.Default.EmptyCredentials == "windows")
{
_rdpClient.UserName = Environment.UserName;
}
else if (Settings.Default.EmptyCredentials == "custom")
{
_rdpClient.UserName = Settings.Default.DefaultUsername;
}
}
else
{
_rdpClient.UserName = userName;
}
if (string.IsNullOrEmpty(password)) _rdpClient.UserName = cred.Username ?? "";
{
if (Settings.Default.EmptyCredentials == "custom")
{
if (Settings.Default.DefaultPassword != "")
{
var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
_rdpClient.AdvancedSettings2.ClearTextPassword =
cryptographyProvider.Decrypt(Settings.Default.DefaultPassword, Runtime.EncryptionKey);
}
}
}
else
{
_rdpClient.AdvancedSettings2.ClearTextPassword = password;
}
if (string.IsNullOrEmpty(domain)) _rdpClient.AdvancedSettings2.ClearTextPassword = cred.Password?.ConvertToUnsecureString() ?? "";
{
if (Settings.Default.EmptyCredentials == "windows") _rdpClient.Domain = cred.Domain ?? "";
{
_rdpClient.Domain = Environment.UserDomainName;
}
else if (Settings.Default.EmptyCredentials == "custom")
{
_rdpClient.Domain = Settings.Default.DefaultDomain;
}
}
else
{
_rdpClient.Domain = domain;
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -3,6 +3,7 @@ using System.Threading;
using System.ComponentModel; using System.ComponentModel;
using System.Net.Sockets; using System.Net.Sockets;
using mRemoteNG.App; using mRemoteNG.App;
using mRemoteNG.Security;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using mRemoteNG.UI.Forms; using mRemoteNG.UI.Forms;
@@ -13,6 +14,8 @@ namespace mRemoteNG.Connection.Protocol.VNC
{ {
public class ProtocolVNC : ProtocolBase, ISupportsViewOnly public class ProtocolVNC : ProtocolBase, ISupportsViewOnly
{ {
private ConnectionInfo Info;
#region Properties #region Properties
public bool SmartSize public bool SmartSize
@@ -188,7 +191,7 @@ namespace mRemoteNG.Connection.Protocol.VNC
_vnc.ConnectComplete += VNCEvent_Connected; _vnc.ConnectComplete += VNCEvent_Connected;
_vnc.ConnectionLost += VNCEvent_Disconnected; _vnc.ConnectionLost += VNCEvent_Disconnected;
FrmMain.ClipboardChanged += VNCEvent_ClipboardChanged; FrmMain.ClipboardChanged += VNCEvent_ClipboardChanged;
if (!Force.HasFlag(ConnectionInfo.Force.NoCredentials) && _info?.Password?.Length > 0) if (!Force.HasFlag(ConnectionInfo.Force.NoCredentials) && Info?.CredentialRecord?.Password?.Length > 0)
{ {
_vnc.GetPassword = VNCEvent_Authenticate; _vnc.GetPassword = VNCEvent_Authenticate;
} }
@@ -268,7 +271,7 @@ namespace mRemoteNG.Connection.Protocol.VNC
private string VNCEvent_Authenticate() private string VNCEvent_Authenticate()
{ {
return _info.Password; return Info.CredentialRecord.Password.ConvertToUnsecureString();
} }
#endregion #endregion

View File

@@ -1,11 +1,11 @@
using mRemoteNG.App; using mRemoteNG.App;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Messages; using mRemoteNG.Messages;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using System;
using System.ComponentModel;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Tree; using mRemoteNG.Tree;
using mRemoteNG.Tree.Root; using mRemoteNG.Tree.Root;
using System;
using System.ComponentModel;
namespace mRemoteNG.Connection namespace mRemoteNG.Connection

View File

@@ -1,4 +1,5 @@
using mRemoteNG.Connection.Protocol; using mRemoteNG.App;
using mRemoteNG.Connection.Protocol;
namespace mRemoteNG.Connection namespace mRemoteNG.Connection
{ {
@@ -16,7 +17,7 @@ namespace mRemoteNG.Connection
if (string.IsNullOrEmpty(connectionInfo.Panel)) if (string.IsNullOrEmpty(connectionInfo.Panel))
connectionInfo.Panel = Language.General; connectionInfo.Panel = Language.General;
connectionInfo.IsQuickConnect = true; connectionInfo.IsQuickConnect = true;
var connectionInitiator = new ConnectionInitiator(); var connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
connectionInitiator.OpenConnection(connectionInfo, ConnectionInfo.Force.DoNotJump); connectionInitiator.OpenConnection(connectionInfo, ConnectionInfo.Force.DoNotJump);
} }
} }

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using mRemoteNG.Security;
namespace mRemoteNG.Credential
{
public class CredentialDomainUserPasswordComparer : IEqualityComparer<ICredentialRecord>
{
public bool Equals(ICredentialRecord x, ICredentialRecord y)
{
if (x == null && y == null)
return true;
if (x == null || y == null)
return false;
return GetHashCode(x) == GetHashCode(y);
}
public int GetHashCode(ICredentialRecord obj)
{
unchecked
{
var hash = 17;
hash = hash * 23 + obj.Username.GetHashCode();
hash = hash * 23 + obj.Domain.GetHashCode();
hash = hash * 23 + obj.Password.ConvertToUnsecureString().GetHashCode();
return hash;
}
}
}
}

View File

@@ -37,7 +37,7 @@ namespace mRemoteNG.Credential
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (!(value is Guid)) return base.ConvertFrom(context, culture, value); if (!(value is Guid)) return base.ConvertFrom(context, culture, value);
var matchedCredentials = Runtime.CredentialProviderCatalog.GetCredentialRecords() var matchedCredentials = Runtime.CredentialService.RepositoryList.GetCredentialRecords()
.Where(record => record.Id.Equals(value)).ToArray(); .Where(record => record.Id.Equals(value)).ToArray();
return matchedCredentials.Any() ? matchedCredentials.First() : null; return matchedCredentials.Any() ? matchedCredentials.First() : null;
} }

View File

@@ -0,0 +1,145 @@
using mRemoteNG.Config;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.Tools;
using mRemoteNG.Tools.CustomCollections;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mRemoteNG.Credential
{
public class CredentialService : ICredentialService
{
private readonly List<ICredentialRepositoryFactory> _repositoryFactories;
private readonly ILoader<IEnumerable<ICredentialRepository>> _loader;
private readonly ISaver<IEnumerable<ICredentialRepository>> _saver;
public ICredentialRepositoryList RepositoryList { get; }
public IReadOnlyCollection<ICredentialRepositoryFactory> RepositoryFactories => _repositoryFactories;
public CredentialService(
ICredentialRepositoryList repositoryList,
List<ICredentialRepositoryFactory> repositoryFactories,
ILoader<IEnumerable<ICredentialRepository>> loader,
ISaver<IEnumerable<ICredentialRepository>> saver)
{
RepositoryList = repositoryList.ThrowIfNull(nameof(repositoryList));
_repositoryFactories = repositoryFactories.ThrowIfNull(nameof(repositoryFactories));
_loader = loader.ThrowIfNull(nameof(loader));
_saver = saver.ThrowIfNull(nameof(saver));
SetupEventHandlers();
}
public void SaveRepositoryList()
{
_saver.Save(RepositoryList);
}
public void LoadRepositoryList()
{
var loadedRepositories = _loader.Load();
foreach (var repository in loadedRepositories)
{
RepositoryList.AddProvider(repository);
}
}
public void AddRepository(ICredentialRepository repository)
{
RepositoryList.AddProvider(repository);
}
public void RemoveRepository(ICredentialRepository repository)
{
RepositoryList.RemoveProvider(repository);
}
public IEnumerable<ICredentialRecord> GetCredentialRecords()
{
return RepositoryList.GetCredentialRecords();
}
public ICredentialRecord GetCredentialRecord(Guid id)
{
return RepositoryList.GetCredentialRecord(id);
}
/// <summary>
/// Returns the <see cref="ICredentialRepository"/> object to use, taking into account
/// any default or replacement credentials that may be used.
/// </summary>
/// <param name="id"></param>
/// <param name="allowDefaultFallback">
/// If True and the <see cref="ICredentialRecord"/> given by <see cref="id"/> cannot be found,
/// we will attempt to use a default credential specified in settings. If False, no default
/// fallback value will be used.
/// </param>
public Optional<ICredentialRecord> GetEffectiveCredentialRecord(Optional<Guid> id, bool allowDefaultFallback = true)
{
var desiredCredentialRecord = GetCredentialRecord(id.FirstOrDefault());
if (desiredCredentialRecord != null)
return desiredCredentialRecord.ToOptional();
if (allowDefaultFallback)
{
if (Properties.Settings.Default.EmptyCredentials == "windows")
return new WindowsDefaultCredentialRecord();
if (Properties.Settings.Default.EmptyCredentials == "custom")
{
var cred = GetCredentialRecord(Properties.Settings.Default.DefaultCredentialRecord);
if (cred != null)
return cred.ToOptional();
}
}
if (!id.Any() || id.FirstOrDefault() == Guid.Empty)
return new NullCredentialRecord();
return new UnavailableCredentialRecord(id.First());
}
/// <summary>
/// Registers an <see cref="ICredentialRepositoryFactory"/> for
/// use throughout the application.
/// </summary>
/// <param name="factory"></param>
public void RegisterRepositoryFactory(ICredentialRepositoryFactory factory)
{
if (_repositoryFactories.Contains(factory))
return;
_repositoryFactories.Add(factory);
}
public Optional<ICredentialRepositoryFactory> GetRepositoryFactoryForConfig(ICredentialRepositoryConfig repositoryConfig)
{
return new Optional<ICredentialRepositoryFactory>(
RepositoryFactories
.FirstOrDefault(factory =>
string.Equals(factory.SupportsConfigType, repositoryConfig.TypeName)));
}
#region Setup
private void SetupEventHandlers()
{
RepositoryList.RepositoriesUpdated += HandleRepositoriesUpdatedEvent;
RepositoryList.CredentialsUpdated += HandleCredentialsUpdatedEvent;
}
private void HandleRepositoriesUpdatedEvent(object sender, CollectionUpdatedEventArgs<ICredentialRepository> collectionUpdatedEventArgs)
{
SaveRepositoryList();
}
private void HandleCredentialsUpdatedEvent(object sender, CollectionUpdatedEventArgs<ICredentialRecord> collectionUpdatedEventArgs)
{
var repo = sender as ICredentialRepository;
repo?.SaveCredentials(repo.Config.Key);
}
#endregion
}
}

View File

@@ -1,36 +1,24 @@
using System.IO; using System.Collections.Generic;
using mRemoteNG.App; using System.IO;
using mRemoteNG.App.Info; using mRemoteNG.App.Info;
using mRemoteNG.Config; using mRemoteNG.Config;
using mRemoteNG.Config.DataProviders; using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.CredentialProviderSerializer; using mRemoteNG.Credential.Repositories;
using mRemoteNG.Config.Serializers.CredentialSerializer;
using mRemoteNG.Security.Factories;
namespace mRemoteNG.Credential namespace mRemoteNG.Credential
{ {
public class CredentialServiceFactory public class CredentialServiceFactory
{ {
// When we get a true CompositionRoot we can move this to that class. We should only require 1 instance of this service at a time // When we get a true CompositionRoot we can move this to that class. We should only require 1 instance of this service at a time
public CredentialServiceFacade Build() public CredentialService Build()
{ {
var cryptoFromSettings = new CryptoProviderFactoryFromSettings(); var repositoryList = new CredentialRepositoryList();
var credRepoSerializer = new XmlCredentialPasswordEncryptorDecorator(
cryptoFromSettings.Build(),
new XmlCredentialRecordSerializer());
var credRepoDeserializer =
new XmlCredentialPasswordDecryptorDecorator(new XmlCredentialRecordDeserializer());
var credentialRepoListPath = Path.Combine(SettingsFileInfo.SettingsPath, "credentialRepositories.xml"); var credentialRepoListPath = Path.Combine(SettingsFileInfo.SettingsPath, "credentialRepositories.xml");
var repoListDataProvider = new FileDataProvider(credentialRepoListPath); var repoListDataProvider = new FileDataProvider(credentialRepoListPath);
var repoListLoader = new CredentialRepositoryListLoader( var repositoryFactories = new List<ICredentialRepositoryFactory>();
repoListDataProvider, var persistor = new CredentialRepositoryListPersistor(repoListDataProvider, repositoryFactories);
new
CredentialRepositoryListDeserializer(credRepoSerializer,
credRepoDeserializer));
var repoListSaver = new CredentialRepositoryListSaver(repoListDataProvider);
return new CredentialServiceFacade(Runtime.CredentialProviderCatalog, repoListLoader, repoListSaver); return new CredentialService(repositoryList, repositoryFactories, persistor, persistor);
} }
} }
} }

View File

@@ -2,16 +2,37 @@
using System.ComponentModel; using System.ComponentModel;
using System.Security; using System.Security;
namespace mRemoteNG.Credential namespace mRemoteNG.Credential
{ {
/// <summary>
/// Represents a named set of username/domain/password information.
/// </summary>
[TypeConverter(typeof(CredentialRecordTypeConverter))] [TypeConverter(typeof(CredentialRecordTypeConverter))]
public interface ICredentialRecord : INotifyPropertyChanged public interface ICredentialRecord : INotifyPropertyChanged
{ {
/// <summary>
/// An Id which uniquely identifies this credential record.
/// </summary>
Guid Id { get; } Guid Id { get; }
/// <summary>
/// A friendly name for this credential record.
/// </summary>
string Title { get; set; } string Title { get; set; }
/// <summary>
/// The username portion of the credential.
/// </summary>
string Username { get; set; } string Username { get; set; }
SecureString Password { get; set; }
/// <summary>
/// The domain portion of the credential.
/// </summary>
string Domain { get; set; } string Domain { get; set; }
/// <summary>
/// The password
/// </summary>
SecureString Password { get; set; }
} }
} }

View File

@@ -9,13 +9,52 @@ namespace mRemoteNG.Credential
{ {
public interface ICredentialRepository public interface ICredentialRepository
{ {
/// <summary>
/// The configuration information for this credential repository.
/// </summary>
ICredentialRepositoryConfig Config { get; } ICredentialRepositoryConfig Config { get; }
/// <summary>
/// A list of the <see cref="ICredentialRecord"/>s provided by this repository.
/// </summary>
IList<ICredentialRecord> CredentialRecords { get; } IList<ICredentialRecord> CredentialRecords { get; }
/// <summary>
/// A friendly name for this repository.
/// </summary>
string Title { get; }
/// <summary>
/// Whether or not this repository has been unlocked and is able to provide
/// credentials.
/// </summary>
bool IsLoaded { get; } bool IsLoaded { get; }
/// <summary>
/// Unlock the repository with the given key and load all available credentials.
/// </summary>
/// <param name="key"></param>
void LoadCredentials(SecureString key); void LoadCredentials(SecureString key);
/// <summary>
/// Save all credentials provided by this repository.
/// </summary>
/// <param name="key"></param>
void SaveCredentials(SecureString key); void SaveCredentials(SecureString key);
/// <summary>
/// Lock and unload all credentials provided by this repository.
/// </summary>
void UnloadCredentials(); void UnloadCredentials();
/// <summary>
/// This event is raised when any changes are made to the assigned <see cref="Config"/>.
/// </summary>
event EventHandler RepositoryConfigUpdated; event EventHandler RepositoryConfigUpdated;
/// <summary>
/// This event is raised when a credential is added or removed from this repository.
/// </summary>
event EventHandler<CollectionUpdatedEventArgs<ICredentialRecord>> CredentialsUpdated; event EventHandler<CollectionUpdatedEventArgs<ICredentialRecord>> CredentialsUpdated;
} }
} }

View File

@@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.Tools;
using mRemoteNG.Tools.CustomCollections; using mRemoteNG.Tools.CustomCollections;
namespace mRemoteNG.Credential namespace mRemoteNG.Credential
@@ -12,7 +14,9 @@ namespace mRemoteNG.Credential
void RemoveProvider(ICredentialRepository credentialProvider); void RemoveProvider(ICredentialRepository credentialProvider);
bool Contains(Guid repositoryId); Optional<ICredentialRepository> GetProvider(Guid id);
bool Contains(ICredentialRepositoryConfig repositoryConfig);
IEnumerable<ICredentialRecord> GetCredentialRecords(); IEnumerable<ICredentialRecord> GetCredentialRecords();

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.Tools;
namespace mRemoteNG.Credential
{
public interface ICredentialService
{
ICredentialRepositoryList RepositoryList { get; }
IReadOnlyCollection<ICredentialRepositoryFactory> RepositoryFactories { get; }
void SaveRepositoryList();
void LoadRepositoryList();
void AddRepository(ICredentialRepository repository);
void RemoveRepository(ICredentialRepository repository);
IEnumerable<ICredentialRecord> GetCredentialRecords();
ICredentialRecord GetCredentialRecord(Guid id);
/// <summary>
/// Returns the <see cref="ICredentialRepository"/> object to use, taking into account
/// any default or replacement credentials that may be used.
/// </summary>
/// <param name="id"></param>
/// <param name="allowDefaultFallback">
/// If True and the <see cref="ICredentialRecord"/> given by <see cref="id"/> cannot be found,
/// we will attempt to use a default credential specified in settings. If False, no default
/// fallback value will be used.
/// </param>
Optional<ICredentialRecord> GetEffectiveCredentialRecord(Optional<Guid> id, bool allowDefaultFallback = true);
/// <summary>
/// Registers an <see cref="ICredentialRepositoryFactory"/> for
/// use throughout the application.
/// </summary>
/// <param name="factory"></param>
void RegisterRepositoryFactory(ICredentialRepositoryFactory factory);
Optional<ICredentialRepositoryFactory> GetRepositoryFactoryForConfig(ICredentialRepositoryConfig repositoryConfig);
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.ComponentModel;
using System.Security;
namespace mRemoteNG.Credential
{
public class CredentialRecord : ICredentialRecord
{
private string _title = "New Credential";
private string _username = "";
private SecureString _password = new SecureString();
private string _domain = "";
public Guid Id { get; } = Guid.NewGuid();
public string Title
{
get { return _title; }
set
{
_title = value;
RaisePropertyChangedEvent(nameof(Title));
}
}
public string Username
{
get { return _username; }
set
{
_username = value;
RaisePropertyChangedEvent(nameof(Username));
}
}
public SecureString Password
{
get { return _password; }
set
{
_password = value;
RaisePropertyChangedEvent(nameof(Password));
}
}
public string Domain
{
get { return _domain; }
set
{
_domain = value;
RaisePropertyChangedEvent(nameof(Domain));
}
}
public CredentialRecord()
{
}
public CredentialRecord(ICredentialRecord otherCredential)
{
Username = otherCredential.Username;
Password = otherCredential.Password;
Domain = otherCredential.Domain;
}
public CredentialRecord(Guid customGuid)
{
Id = customGuid;
}
public override string ToString()
{
return Title;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChangedEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Security;
namespace mRemoteNG.Credential
{
public class NullCredentialRecord : ICredentialRecord
{
public Guid Id { get; } = Guid.Empty;
[ReadOnly(true)]
public string Title { get; set; } = $"--{Language.None}--";
[ReadOnly(true)]
public string Username { get; set; } = string.Empty;
[ReadOnly(true)]
public SecureString Password { get; set; } = new SecureString();
[ReadOnly(true)]
public string Domain { get; set; } = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
public override string ToString()
{
return Title;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Security;
namespace mRemoteNG.Credential
{
public class UnavailableCredentialRecord : ICredentialRecord
{
public Guid Id { get; }
[ReadOnly(true)]
public string Title { get; set; } = Language.CredentialUnavailable;
[ReadOnly(true)]
public string Username { get; set; } = string.Empty;
[ReadOnly(true)]
public SecureString Password { get; set; } = new SecureString();
[ReadOnly(true)]
public string Domain { get; set; } = string.Empty;
public UnavailableCredentialRecord(Guid id)
{
Id = id;
}
public override string ToString() => Language.CredentialUnavailable;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.ComponentModel;
using System.Security;
namespace mRemoteNG.Credential
{
public class WindowsDefaultCredentialRecord : ICredentialRecord
{
private readonly string _title;
public Guid Id { get; } = Guid.NewGuid();
public string Title
{
get => _title;
set {}
}
public string Username
{
get => Environment.UserName;
set {}
}
public SecureString Password
{
get => new SecureString();
set {}
}
public string Domain
{
get => Environment.UserDomainName;
set {}
}
public WindowsDefaultCredentialRecord()
{
// TODO: localizations
_title = "Windows Default Credentials";
}
public override string ToString()
{
return Title;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChangedEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security; using System.Security;
using mRemoteNG.Tools;
namespace mRemoteNG.Credential.Repositories namespace mRemoteNG.Credential.Repositories
{ {
@@ -10,7 +11,7 @@ namespace mRemoteNG.Credential.Repositories
private readonly List<ICredentialRepository> _repositories = new List<ICredentialRepository>(); private readonly List<ICredentialRepository> _repositories = new List<ICredentialRepository>();
public IEnumerable<ICredentialRepository> Repositories => _repositories; public IEnumerable<ICredentialRepository> Repositories => _repositories;
public ICredentialRepository SelectedRepository { get; set; } public Optional<ICredentialRepository> SelectedRepository { get; set; } = Optional<ICredentialRepository>.Empty;
public CompositeRepositoryUnlocker(IEnumerable<ICredentialRepository> repositories) public CompositeRepositoryUnlocker(IEnumerable<ICredentialRepository> repositories)
{ {
@@ -23,7 +24,8 @@ namespace mRemoteNG.Credential.Repositories
public void Unlock(SecureString key) public void Unlock(SecureString key)
{ {
SelectedRepository.LoadCredentials(key); if (SelectedRepository.Any())
SelectedRepository.First().LoadCredentials(key);
} }
public void SelectNextLockedRepository() public void SelectNextLockedRepository()
@@ -31,10 +33,10 @@ namespace mRemoteNG.Credential.Repositories
SelectedRepository = GetNextLockedRepo(); SelectedRepository = GetNextLockedRepo();
} }
private ICredentialRepository GetNextLockedRepo() private Optional<ICredentialRepository> GetNextLockedRepo()
{ {
var newOrder = OrderListForNextLockedRepo(); var newOrder = OrderListForNextLockedRepo();
return newOrder.Any() ? newOrder.First() : null; return new Optional<ICredentialRepository>(newOrder.FirstOrDefault());
} }
private IList<ICredentialRepository> OrderListForNextLockedRepo() private IList<ICredentialRepository> OrderListForNextLockedRepo()
@@ -67,7 +69,10 @@ namespace mRemoteNG.Credential.Repositories
private int GetNewListStartIndex() private int GetNewListStartIndex()
{ {
var currentItemIndex = _repositories.IndexOf(SelectedRepository); if (!SelectedRepository.Any())
return 0;
var currentItemIndex = _repositories.IndexOf(SelectedRepository.First());
return currentItemIndex + 1; return currentItemIndex + 1;
} }
} }

View File

@@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using mRemoteNG.Tools;
using mRemoteNG.Tools.CustomCollections; using mRemoteNG.Tools.CustomCollections;
namespace mRemoteNG.Credential.Repositories namespace mRemoteNG.Credential.Repositories
@@ -15,7 +16,9 @@ namespace mRemoteNG.Credential.Repositories
public void AddProvider(ICredentialRepository credentialProvider) public void AddProvider(ICredentialRepository credentialProvider)
{ {
if (Contains(credentialProvider.Config.Id)) return; if (Contains(credentialProvider.Config))
return;
_credentialProviders.Add(credentialProvider); _credentialProviders.Add(credentialProvider);
credentialProvider.CredentialsUpdated += RaiseCredentialsUpdatedEvent; credentialProvider.CredentialsUpdated += RaiseCredentialsUpdatedEvent;
credentialProvider.RepositoryConfigUpdated += OnRepoConfigChanged; credentialProvider.RepositoryConfigUpdated += OnRepoConfigChanged;
@@ -24,16 +27,27 @@ namespace mRemoteNG.Credential.Repositories
public void RemoveProvider(ICredentialRepository credentialProvider) public void RemoveProvider(ICredentialRepository credentialProvider)
{ {
if (!Contains(credentialProvider.Config.Id)) return; if (!Contains(credentialProvider.Config))
return;
credentialProvider.CredentialsUpdated -= RaiseCredentialsUpdatedEvent; credentialProvider.CredentialsUpdated -= RaiseCredentialsUpdatedEvent;
credentialProvider.RepositoryConfigUpdated -= OnRepoConfigChanged; credentialProvider.RepositoryConfigUpdated -= OnRepoConfigChanged;
_credentialProviders.Remove(credentialProvider); _credentialProviders.Remove(credentialProvider);
RaiseRepositoriesUpdatedEvent(ActionType.Removed, new[] {credentialProvider}); RaiseRepositoriesUpdatedEvent(ActionType.Removed, new[] {credentialProvider});
} }
public bool Contains(Guid repositoryId) public Optional<ICredentialRepository> GetProvider(Guid id)
{ {
return _credentialProviders.Any(repo => repo.Config.Id == repositoryId); return _credentialProviders
.FirstOrDefault(repo => repo.Config.Id.Equals(id))
.ToOptional();
}
public bool Contains(ICredentialRepositoryConfig repositoryConfig)
{
return _credentialProviders.Any(repo =>
repo.Config.Id == repositoryConfig.Id ||
string.Equals(repo.Config.Source, repositoryConfig.Source));
} }
public IEnumerable<ICredentialRecord> GetCredentialRecords() public IEnumerable<ICredentialRecord> GetCredentialRecords()

View File

@@ -0,0 +1,21 @@
using System;
namespace mRemoteNG.Credential.Repositories
{
public class CredentialRepositoryTypeNotSupportedException : Exception
{
public CredentialRepositoryTypeNotSupportedException()
{
}
public CredentialRepositoryTypeNotSupportedException(string message)
: base(message)
{
}
public CredentialRepositoryTypeNotSupportedException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -6,10 +6,33 @@ namespace mRemoteNG.Credential.Repositories
{ {
public interface ICredentialRepositoryConfig : INotifyPropertyChanged public interface ICredentialRepositoryConfig : INotifyPropertyChanged
{ {
/// <summary>
/// An Id which uniquely identifies this credential repository.
/// </summary>
Guid Id { get; } Guid Id { get; }
/// <summary>
/// A friendly name for this credential repository
/// </summary>
string Title { get; set; } string Title { get; set; }
/// <summary>
/// A name which uniquely identifies the type of credential repository this
/// config represents. This is used for determining which <see cref="ICredentialRepositoryFactory"/>
/// to use to create the <see cref="ICredentialRepository"/>.
/// </summary>
string TypeName { get; } string TypeName { get; }
/// <summary>
/// A string representing how to access the persisted credential records.
/// This may be a file path, URL, database connection string, etc. depending
/// on the implementation of the <see cref="ICredentialRepository"/>.
/// </summary>
string Source { get; set; } string Source { get; set; }
/// <summary>
/// The password necessary to unlock and access the underlying repository.
/// </summary>
SecureString Key { get; set; } SecureString Key { get; set; }
} }
} }

View File

@@ -0,0 +1,20 @@
namespace mRemoteNG.Credential.Repositories
{
public interface ICredentialRepositoryFactory
{
/// <summary>
/// The <see cref="ICredentialRepositoryConfig.TypeName"/> that this factory can build.
/// </summary>
string SupportsConfigType { get; }
/// <summary>
/// Builds a new <see cref="ICredentialRepositoryFactory"/> given the <see cref="ICredentialRepositoryConfig"/>
/// that describes it. The <see cref="ICredentialRepositoryConfig.TypeName"/> must match this factory's
/// <see cref="SupportsConfigType"/> property.
/// </summary>
/// <param name="config"></param>
/// <param name="isLoaded"></param>
/// <returns></returns>
ICredentialRepository Build(ICredentialRepositoryConfig config, bool isLoaded = false);
}
}

View File

@@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Security; using System.Security;
using mRemoteNG.Config; using mRemoteNG.Config;
using mRemoteNG.Tools;
using mRemoteNG.Tools.CustomCollections; using mRemoteNG.Tools.CustomCollections;
namespace mRemoteNG.Credential.Repositories namespace mRemoteNG.Credential.Repositories
@@ -15,26 +16,36 @@ namespace mRemoteNG.Credential.Repositories
public ICredentialRepositoryConfig Config { get; } public ICredentialRepositoryConfig Config { get; }
public IList<ICredentialRecord> CredentialRecords { get; } public IList<ICredentialRecord> CredentialRecords { get; }
public string Title => Config.Title;
public bool IsLoaded { get; private set; } public bool IsLoaded { get; private set; }
public XmlCredentialRepository(ICredentialRepositoryConfig config, /// <summary>
CredentialRecordSaver credentialRecordSaver, /// Creates a new <see cref="XmlCredentialRepository"/> instance,
CredentialRecordLoader credentialRecordLoader) /// providing access to load and save credentials stored in an XML
/// format.
/// </summary>
/// <param name="config">
/// The config representing this repository
/// </param>
/// <param name="credentialRecordSaver"></param>
/// <param name="credentialRecordLoader"></param>
/// <param name="isLoaded">
/// Does this instance represent a repository that is already loaded?
/// </param>
public XmlCredentialRepository(
ICredentialRepositoryConfig config,
CredentialRecordSaver credentialRecordSaver,
CredentialRecordLoader credentialRecordLoader,
bool isLoaded = false)
{ {
if (config == null) Config = config.ThrowIfNull(nameof(config));
throw new ArgumentNullException(nameof(config)); _credentialRecordSaver = credentialRecordSaver.ThrowIfNull(nameof(credentialRecordSaver));
if (credentialRecordSaver == null) _credentialRecordLoader = credentialRecordLoader.ThrowIfNull(nameof(credentialRecordLoader));
throw new ArgumentNullException(nameof(credentialRecordSaver)); IsLoaded = isLoaded;
if (credentialRecordLoader == null)
throw new ArgumentNullException(nameof(credentialRecordLoader));
Config = config;
CredentialRecords = new FullyObservableCollection<ICredentialRecord>(); CredentialRecords = new FullyObservableCollection<ICredentialRecord>();
((FullyObservableCollection<ICredentialRecord>)CredentialRecords).CollectionUpdated += ((FullyObservableCollection<ICredentialRecord>) CredentialRecords).CollectionUpdated += RaiseCredentialsUpdatedEvent;
RaiseCredentialsUpdatedEvent;
Config.PropertyChanged += (sender, args) => RaiseRepositoryConfigUpdatedEvent(args); Config.PropertyChanged += (sender, args) => RaiseRepositoryConfigUpdatedEvent(args);
_credentialRecordSaver = credentialRecordSaver;
_credentialRecordLoader = credentialRecordLoader;
} }
public void LoadCredentials(SecureString key) public void LoadCredentials(SecureString key)
@@ -42,7 +53,9 @@ namespace mRemoteNG.Credential.Repositories
var credentials = _credentialRecordLoader.Load(key); var credentials = _credentialRecordLoader.Load(key);
foreach (var newCredential in credentials) foreach (var newCredential in credentials)
{ {
if (ThisIsADuplicateCredentialRecord(newCredential)) continue; if (ThisIsADuplicateCredentialRecord(newCredential))
continue;
CredentialRecords.Add(newCredential); CredentialRecords.Add(newCredential);
} }
@@ -63,7 +76,9 @@ namespace mRemoteNG.Credential.Repositories
public void SaveCredentials(SecureString key) public void SaveCredentials(SecureString key)
{ {
if (!IsLoaded) return; if (!IsLoaded)
return;
_credentialRecordSaver.Save(CredentialRecords, key); _credentialRecordSaver.Save(CredentialRecords, key);
} }

View File

@@ -1,55 +1,41 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Xml.Linq;
using mRemoteNG.Config; using mRemoteNG.Config;
using mRemoteNG.Config.DataProviders; using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers; using mRemoteNG.Config.Serializers;
using mRemoteNG.Tools;
namespace mRemoteNG.Credential.Repositories namespace mRemoteNG.Credential.Repositories
{ {
public class XmlCredentialRepositoryFactory public class XmlCredentialRepositoryFactory : ICredentialRepositoryFactory
{ {
private readonly ISecureSerializer<IEnumerable<ICredentialRecord>, string> _serializer; private readonly ISecureSerializer<IEnumerable<ICredentialRecord>, string> _serializer;
private readonly ISecureDeserializer<string, IEnumerable<ICredentialRecord>> _deserializer; private readonly ISecureDeserializer<string, IEnumerable<ICredentialRecord>> _deserializer;
public XmlCredentialRepositoryFactory(ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer, public XmlCredentialRepositoryFactory(
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer) ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer,
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer)
{ {
if (serializer == null) _serializer = serializer.ThrowIfNull(nameof(serializer));
throw new ArgumentNullException(nameof(serializer)); _deserializer = deserializer.ThrowIfNull(nameof(deserializer));
if (deserializer == null)
throw new ArgumentNullException(nameof(deserializer));
_serializer = serializer;
_deserializer = deserializer;
} }
public ICredentialRepository Build(ICredentialRepositoryConfig config) public string SupportsConfigType { get; } = "Xml";
{
return BuildXmlRepo(config);
}
public ICredentialRepository Build(XElement repositoryXElement) /// <summary>
{ /// Creates a new <see cref="XmlCredentialRepository"/> instance for
var stringId = repositoryXElement.Attribute("Id")?.Value; /// the given <see cref="ICredentialRepositoryConfig"/>.
Guid id; /// </summary>
Guid.TryParse(stringId, out id); /// <param name="config"></param>
if (id.Equals(Guid.Empty)) id = Guid.NewGuid(); /// <param name="isLoaded">
var config = new CredentialRepositoryConfig(id) /// Does this instance represent a repository that is already loaded?
{ /// </param>
TypeName = repositoryXElement.Attribute("TypeName")?.Value, public ICredentialRepository Build(ICredentialRepositoryConfig config, bool isLoaded = false)
Title = repositoryXElement.Attribute("Title")?.Value,
Source = repositoryXElement.Attribute("Source")?.Value
};
return BuildXmlRepo(config);
}
private ICredentialRepository BuildXmlRepo(ICredentialRepositoryConfig config)
{ {
var dataProvider = new FileDataProvider(config.Source); var dataProvider = new FileDataProvider(config.Source);
var saver = new CredentialRecordSaver(dataProvider, _serializer); var saver = new CredentialRecordSaver(dataProvider, _serializer);
var loader = new CredentialRecordLoader(dataProvider, _deserializer); var loader = new CredentialRecordLoader(dataProvider, _deserializer);
return new XmlCredentialRepository(config, saver, loader);
return new XmlCredentialRepository(config, saver, loader, isLoaded);
} }
} }
} }

View File

@@ -510,6 +510,15 @@ namespace mRemoteNG {
} }
} }
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Back ähnelt.
/// </summary>
internal static string Back {
get {
return ResourceManager.GetString("Back", resourceCulture);
}
}
/// <summary> /// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Default Inheritance ähnelt. /// Sucht eine lokalisierte Zeichenfolge, die Default Inheritance ähnelt.
/// </summary> /// </summary>
@@ -1258,6 +1267,15 @@ namespace mRemoteNG {
} }
} }
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Continue ähnelt.
/// </summary>
internal static string Continue {
get {
return ResourceManager.GetString("Continue", resourceCulture);
}
}
/// <summary> /// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Copy ähnelt. /// Sucht eine lokalisierte Zeichenfolge, die Copy ähnelt.
/// </summary> /// </summary>
@@ -3459,6 +3477,15 @@ namespace mRemoteNG {
} }
} }
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die TODO ähnelt.
/// </summary>
internal static string PropertyDescriptionCredentials {
get {
return ResourceManager.GetString("PropertyDescriptionCredentials", resourceCulture);
}
}
/// <summary> /// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Put your notes or a description for the host here. ähnelt. /// Sucht eine lokalisierte Zeichenfolge, die Put your notes or a description for the host here. ähnelt.
/// </summary> /// </summary>
@@ -4893,6 +4920,15 @@ namespace mRemoteNG {
} }
} }
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Lock ähnelt.
/// </summary>
internal static string RepoLock {
get {
return ResourceManager.GetString("RepoLock", resourceCulture);
}
}
/// <summary> /// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Report a Bug ähnelt. /// Sucht eine lokalisierte Zeichenfolge, die Report a Bug ähnelt.
/// </summary> /// </summary>
@@ -4902,6 +4938,15 @@ namespace mRemoteNG {
} }
} }
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Unlock ähnelt.
/// </summary>
internal static string RepoUnlock {
get {
return ResourceManager.GetString("RepoUnlock", resourceCulture);
}
}
/// <summary> /// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Reset layout ähnelt. /// Sucht eine lokalisierte Zeichenfolge, die Reset layout ähnelt.
/// </summary> /// </summary>

View File

@@ -2153,4 +2153,19 @@ Nightly Channel includes Alphas, Betas &amp; Release Candidates.</value>
<data name="SmartCard" xml:space="preserve"> <data name="SmartCard" xml:space="preserve">
<value>SmartCard</value> <value>SmartCard</value>
</data> </data>
<data name="PropertyDescriptionCredentials" xml:space="preserve">
<value>TODO</value>
</data>
<data name="RepoLock" xml:space="preserve">
<value>Lock</value>
</data>
<data name="RepoUnlock" xml:space="preserve">
<value>Unlock</value>
</data>
<data name="Back" xml:space="preserve">
<value>Back</value>
</data>
<data name="Continue" xml:space="preserve">
<value>Continue</value>
</data>
</root> </root>

View File

@@ -200,6 +200,16 @@ namespace mRemoteNG.Properties {
} }
} }
/// <summary>
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Edit_16x {
get {
object obj = ResourceManager.GetObject("Edit_16x", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary> /// <summary>
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
/// </summary> /// </summary>
@@ -766,6 +776,16 @@ namespace mRemoteNG.Properties {
} }
} }
/// <summary>
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Unlock_16x {
get {
object obj = ResourceManager.GetObject("Unlock_16x", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary> /// <summary>
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
/// </summary> /// </summary>

View File

@@ -337,4 +337,10 @@
<data name="Property_16x" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="Property_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Property_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\Property_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>
<data name="Edit_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Edit_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Unlock_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Unlock_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root> </root>

View File

@@ -251,42 +251,6 @@ namespace mRemoteNG.Properties {
} }
} }
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string DefaultUsername {
get {
return ((string)(this["DefaultUsername"]));
}
set {
this["DefaultUsername"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string DefaultPassword {
get {
return ((string)(this["DefaultPassword"]));
}
set {
this["DefaultPassword"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string DefaultDomain {
get {
return ((string)(this["DefaultDomain"]));
}
set {
this["DefaultDomain"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()] [global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")] [global::System.Configuration.DefaultSettingValueAttribute("False")]
@@ -2771,6 +2735,18 @@ namespace mRemoteNG.Properties {
} }
} }
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool CloseCredentialUnlockerDialogAfterLastUnlock {
get {
return ((bool)(this["CloseCredentialUnlockerDialogAfterLastUnlock"]));
}
set {
this["CloseCredentialUnlockerDialogAfterLastUnlock"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()] [global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")] [global::System.Configuration.DefaultSettingValueAttribute("False")]
@@ -2797,6 +2773,18 @@ namespace mRemoteNG.Properties {
[global::System.Configuration.UserScopedSettingAttribute()] [global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("00000000-0000-0000-0000-000000000000")]
public global::System.Guid DefaultCredentialRecord {
get {
return ((global::System.Guid)(this["DefaultCredentialRecord"]));
}
set {
this["DefaultCredentialRecord"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")] [global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool AlwaysShowConnectionTabs { public bool AlwaysShowConnectionTabs {
get { get {

View File

@@ -59,15 +59,6 @@
<Setting Name="EmptyCredentials" Type="System.String" Scope="User"> <Setting Name="EmptyCredentials" Type="System.String" Scope="User">
<Value Profile="(Default)">noinfo</Value> <Value Profile="(Default)">noinfo</Value>
</Setting> </Setting>
<Setting Name="DefaultUsername" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="DefaultPassword" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="DefaultDomain" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="UseCustomPuttyPath" Type="System.Boolean" Scope="User"> <Setting Name="UseCustomPuttyPath" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value> <Value Profile="(Default)">False</Value>
</Setting> </Setting>
@@ -689,11 +680,17 @@
<Setting Name="StartUpPanelName" Type="System.String" Scope="User"> <Setting Name="StartUpPanelName" Type="System.String" Scope="User">
<Value Profile="(Default)">General</Value> <Value Profile="(Default)">General</Value>
</Setting> </Setting>
<Setting Name="CloseCredentialUnlockerDialogAfterLastUnlock" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="TrackActiveConnectionInConnectionTree" Type="System.Boolean" Scope="User"> <Setting Name="TrackActiveConnectionInConnectionTree" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value> <Value Profile="(Default)">False</Value>
</Setting> </Setting>
<Setting Name="PlaceSearchBarAboveConnectionTree" Type="System.Boolean" Scope="User"> <Setting Name="PlaceSearchBarAboveConnectionTree" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value> <Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="DefaultCredentialRecord" Type="System.Guid" Scope="User">
<Value Profile="(Default)">00000000-0000-0000-0000-000000000000</Value>
</Setting> </Setting>
<Setting Name="AlwaysShowConnectionTabs" Type="System.Boolean" Scope="User"> <Setting Name="AlwaysShowConnectionTabs" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value> <Value Profile="(Default)">True</Value>

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -36,9 +36,9 @@
<xs:attribute name="Descr" type="xs:string" use="required" /> <xs:attribute name="Descr" type="xs:string" use="required" />
<xs:attribute name="Icon" type="xs:string" use="required" /> <xs:attribute name="Icon" type="xs:string" use="required" />
<xs:attribute name="Panel" type="xs:string" use="required" /> <xs:attribute name="Panel" type="xs:string" use="required" />
<xs:attribute name="Username" type="xs:string" use="required" />
<xs:attribute name="Domain" type="xs:string" use="required" /> <xs:attribute name="CredentialId" type="xs:string" use="required" />
<xs:attribute name="Password" type="xs:string" use="required" />
<xs:attribute name="Hostname" type="xs:string" use="required" /> <xs:attribute name="Hostname" type="xs:string" use="required" />
<xs:attribute name="Protocol" type="xs:string" use="required" /> <xs:attribute name="Protocol" type="xs:string" use="required" />
<xs:attribute name="RdpVersion" type="xs:string" use="required" /> <xs:attribute name="RdpVersion" type="xs:string" use="required" />
@@ -115,9 +115,9 @@
<xs:attribute name="InheritDisableCursorShadow" type="xs:boolean" use="optional" /> <xs:attribute name="InheritDisableCursorShadow" type="xs:boolean" use="optional" />
<xs:attribute name="InheritDisableCursorBlinking" type="xs:boolean" use="optional" /> <xs:attribute name="InheritDisableCursorBlinking" type="xs:boolean" use="optional" />
<xs:attribute name="InheritDomain" type="xs:boolean" use="optional" /> <xs:attribute name="InheritDomain" type="xs:boolean" use="optional" />
<xs:attribute name="InheritCredentialId" type="xs:boolean" use="optional" />
<xs:attribute name="InheritIcon" type="xs:boolean" use="optional" /> <xs:attribute name="InheritIcon" type="xs:boolean" use="optional" />
<xs:attribute name="InheritPanel" type="xs:boolean" use="optional" /> <xs:attribute name="InheritPanel" type="xs:boolean" use="optional" />
<xs:attribute name="InheritPassword" type="xs:boolean" use="optional" />
<xs:attribute name="InheritPort" type="xs:boolean" use="optional" /> <xs:attribute name="InheritPort" type="xs:boolean" use="optional" />
<xs:attribute name="InheritProtocol" type="xs:boolean" use="optional" /> <xs:attribute name="InheritProtocol" type="xs:boolean" use="optional" />
<xs:attribute name="InheritRdpVersion" type="xs:boolean" use="optional" /> <xs:attribute name="InheritRdpVersion" type="xs:boolean" use="optional" />
@@ -137,7 +137,6 @@
<xs:attribute name="InheritAutomaticResize" type="xs:boolean" use="optional" /> <xs:attribute name="InheritAutomaticResize" type="xs:boolean" use="optional" />
<xs:attribute name="InheritUseConsoleSession" type="xs:boolean" use="optional" /> <xs:attribute name="InheritUseConsoleSession" type="xs:boolean" use="optional" />
<xs:attribute name="InheritRenderingEngine" type="xs:boolean" use="optional" /> <xs:attribute name="InheritRenderingEngine" type="xs:boolean" use="optional" />
<xs:attribute name="InheritUsername" type="xs:boolean" use="optional" />
<xs:attribute name="InheritICAEncryptionStrength" type="xs:boolean" use="optional" /> <xs:attribute name="InheritICAEncryptionStrength" type="xs:boolean" use="optional" />
<xs:attribute name="InheritRDPAuthenticationLevel" type="xs:boolean" use="optional" /> <xs:attribute name="InheritRDPAuthenticationLevel" type="xs:boolean" use="optional" />
<xs:attribute name="InheritRDPMinutesToIdleTimeout" type="xs:boolean" use="optional" /> <xs:attribute name="InheritRDPMinutesToIdleTimeout" type="xs:boolean" use="optional" />

View File

@@ -3,7 +3,6 @@ using System.Drawing;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Xml; using System.Xml;
using mRemoteNG.Themes;
namespace mRemoteNG.Themes namespace mRemoteNG.Themes
{ {

View File

@@ -15,7 +15,7 @@ namespace mRemoteNG.Tools
public MouseEventHandler MouseUpEventHandler { get; set; } public MouseEventHandler MouseUpEventHandler { get; set; }
public IEnumerable<ToolStripDropDownItem> CreateToolStripDropDownItems(ConnectionTreeModel connectionTreeModel) public IEnumerable<ToolStripDropDownItem> CreateToolStripDropDownItems(IConnectionTreeModel connectionTreeModel)
{ {
var rootNodes = connectionTreeModel.RootNodes; var rootNodes = connectionTreeModel.RootNodes;
return CreateToolStripDropDownItems(rootNodes); return CreateToolStripDropDownItems(rootNodes);

View File

@@ -10,7 +10,7 @@ namespace mRemoteNG.Tools.CustomCollections
where T : INotifyPropertyChanged where T : INotifyPropertyChanged
{ {
private readonly IList<T> _list = new List<T>(); private readonly IList<T> _list = new List<T>();
private bool _eventsAllowed; private bool _eventsAllowed = true;
public int Count => _list.Count; public int Count => _list.Count;
public bool IsReadOnly => _list.IsReadOnly; public bool IsReadOnly => _list.IsReadOnly;

View File

@@ -1,12 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using mRemoteNG.Connection;
using mRemoteNG.Container;
namespace mRemoteNG.Tools namespace mRemoteNG.Tools
{ {
public static class Extensions public static class Extensions
{ {
public static Optional<T> Maybe<T>(this T value) public static Optional<T> ToOptional<T>(this T value)
{ {
return new Optional<T>(value); return new Optional<T>(value);
} }
@@ -69,5 +71,19 @@ namespace mRemoteNG.Tools
return collection; return collection;
} }
public static IEnumerable<ConnectionInfo> FlattenConnectionTree(this IEnumerable<ConnectionInfo> connections)
{
foreach (var item in connections)
{
yield return item;
if (!(item is ContainerInfo container))
continue;
foreach (var child in FlattenConnectionTree(container.Children))
yield return child;
}
}
} }
} }

View File

@@ -16,7 +16,7 @@ namespace mRemoteNG.Tools
{ {
public class ExternalTool : INotifyPropertyChanged public class ExternalTool : INotifyPropertyChanged
{ {
private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator(); private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
private string _displayName; private string _displayName;
private string _fileName; private string _fileName;
private bool _waitForExit; private bool _waitForExit;
@@ -152,7 +152,7 @@ namespace mRemoteNG.Tools
private void SetProcessProperties(Process process, ConnectionInfo startConnectionInfo) private void SetProcessProperties(Process process, ConnectionInfo startConnectionInfo)
{ {
var argParser = new ExternalToolArgumentParser(startConnectionInfo); var argParser = new ExternalToolArgumentParser(startConnectionInfo, Runtime.CredentialService);
process.StartInfo.UseShellExecute = true; process.StartInfo.UseShellExecute = true;
process.StartInfo.FileName = argParser.ParseArguments(FileName); process.StartInfo.FileName = argParser.ParseArguments(FileName);
process.StartInfo.Arguments = argParser.ParseArguments(Arguments); process.StartInfo.Arguments = argParser.ParseArguments(Arguments);

View File

@@ -1,9 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using mRemoteNG.App; using System.Linq;
using System.Text.RegularExpressions;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.Properties; using mRemoteNG.Credential;
using mRemoteNG.Security.SymmetricEncryption; using mRemoteNG.Security;
using mRemoteNG.Tools.Cmdline; using mRemoteNG.Tools.Cmdline;
namespace mRemoteNG.Tools namespace mRemoteNG.Tools
@@ -11,10 +12,12 @@ namespace mRemoteNG.Tools
public class ExternalToolArgumentParser public class ExternalToolArgumentParser
{ {
private readonly ConnectionInfo _connectionInfo; private readonly ConnectionInfo _connectionInfo;
private readonly ICredentialService _credentialService;
public ExternalToolArgumentParser(ConnectionInfo connectionInfo) public ExternalToolArgumentParser(ConnectionInfo connectionInfo, ICredentialService credentialService)
{ {
_connectionInfo = connectionInfo; _connectionInfo = connectionInfo;
_credentialService = credentialService.ThrowIfNull(nameof(credentialService));
} }
public string ParseArguments(string input) public string ParseArguments(string input)
@@ -162,56 +165,55 @@ namespace mRemoteNG.Tools
private string GetVariableReplacement(string variable, string original) private string GetVariableReplacement(string variable, string original)
{ {
var replacement = ""; var normalizedVariable = variable.ToLowerInvariant();
if (_connectionInfo == null) return replacement;
switch (variable.ToLowerInvariant())
{
case "name":
replacement = _connectionInfo.Name;
break;
case "hostname":
replacement = _connectionInfo.Hostname;
break;
case "port":
replacement = Convert.ToString(_connectionInfo.Port);
break;
case "username":
replacement = _connectionInfo.Username;
if (string.IsNullOrEmpty(replacement))
if (Settings.Default.EmptyCredentials == "windows")
replacement = Environment.UserName;
else if (Settings.Default.EmptyCredentials == "custom")
replacement = Settings.Default.DefaultUsername;
break;
case "password":
replacement = _connectionInfo.Password;
if (string.IsNullOrEmpty(replacement) && Settings.Default.EmptyCredentials == "custom")
replacement = new LegacyRijndaelCryptographyProvider()
.Decrypt(Convert.ToString(Settings.Default.DefaultPassword),
Runtime.EncryptionKey);
break;
case "domain":
replacement = _connectionInfo.Domain;
if (string.IsNullOrEmpty(replacement))
if (Settings.Default.EmptyCredentials == "windows")
replacement = Environment.UserDomainName;
else if (Settings.Default.EmptyCredentials == "custom")
replacement = Settings.Default.DefaultDomain;
break;
case "description":
replacement = _connectionInfo.Description;
break;
case "macaddress":
replacement = _connectionInfo.MacAddress;
break;
case "userfield":
replacement = _connectionInfo.UserField;
break;
default:
return original;
}
return replacement; if (normalizedVariable == "name")
return _connectionInfo?.Name ?? "";
if (normalizedVariable == "hostname")
return _connectionInfo?.Hostname ?? "";
if (normalizedVariable == "port")
return _connectionInfo?.Port.ToString() ?? "";
if (normalizedVariable == "description")
return _connectionInfo?.Description ?? "";
if (normalizedVariable == "macaddress")
return _connectionInfo?.MacAddress ?? "";
if (normalizedVariable == "userfield")
return _connectionInfo?.UserField ?? "";
if (normalizedVariable.StartsWith("username"))
return GetCredentialToUse(normalizedVariable)
.FirstOrDefault()?
.Username;
if (normalizedVariable.StartsWith("password"))
return GetCredentialToUse(normalizedVariable)
.FirstOrDefault()?
.Password.ConvertToUnsecureString();
if (normalizedVariable.StartsWith("domain"))
return GetCredentialToUse(normalizedVariable)
.FirstOrDefault()?
.Domain;
return original;
}
private Optional<ICredentialRecord> GetCredentialToUse(string variable)
{
var specifiedCred = GetSpecifiedCredential(variable);
return specifiedCred.Any()
? specifiedCred
: _credentialService.GetEffectiveCredentialRecord(_connectionInfo?.CredentialRecordId ?? Optional<Guid>.Empty);
}
private Optional<ICredentialRecord> GetSpecifiedCredential(string variable)
{
var match = Regex.Match(variable.Replace("-", string.Empty), @":(?<id>[0-9a-fA-F]{7,32})$");
if (!match.Success)
return Optional<ICredentialRecord>.Empty;
var id = match.Groups["id"].Value;
return _credentialService
.GetCredentialRecords()
.FirstOrDefault(r => r.Id.ToString("N").StartsWith(id))
.ToOptional();
} }
private string PerformReplacements(string input, List<Replacement> replacements) private string PerformReplacements(string input, List<Replacement> replacements)

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Security.AccessControl; using System.Security.AccessControl;
using Microsoft.Win32; using Microsoft.Win32;
using mRemoteNG.App; using mRemoteNG.App;

View File

@@ -15,7 +15,7 @@ namespace mRemoteNG.Tools
private readonly NotifyIcon _nI; private readonly NotifyIcon _nI;
private readonly ContextMenuStrip _cMen; private readonly ContextMenuStrip _cMen;
private readonly ToolStripMenuItem _cMenCons; private readonly ToolStripMenuItem _cMenCons;
private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator(); private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
private static readonly FrmMain FrmMain = FrmMain.Default; private static readonly FrmMain FrmMain = FrmMain.Default;
public bool Disposed { get; private set; } public bool Disposed { get; private set; }

View File

@@ -43,7 +43,8 @@ namespace mRemoteNG.Tools
return new Optional<T>(value); return new Optional<T>(value);
} }
public static Optional<TOut> FromNullable<TOut>(TOut? value) where TOut : struct public static Optional<TOut> FromNullable<TOut>(TOut? value)
where TOut : struct
{ {
return value.HasValue return value.HasValue
? new Optional<TOut>(value.Value) ? new Optional<TOut>(value.Value)

View File

@@ -9,7 +9,7 @@ using mRemoteNG.Tree.Root;
namespace mRemoteNG.Tree namespace mRemoteNG.Tree
{ {
public sealed class ConnectionTreeModel : INotifyCollectionChanged, INotifyPropertyChanged public sealed class ConnectionTreeModel : IConnectionTreeModel
{ {
public List<ContainerInfo> RootNodes { get; } = new List<ContainerInfo>(); public List<ContainerInfo> RootNodes { get; } = new List<ContainerInfo>();
@@ -48,7 +48,7 @@ namespace mRemoteNG.Tree
public IEnumerable<ConnectionInfo> GetRecursiveChildList(ContainerInfo container) public IEnumerable<ConnectionInfo> GetRecursiveChildList(ContainerInfo container)
{ {
return container.GetRecursiveChildList(); return container?.GetRecursiveChildList() ?? new ConnectionInfo[0];
} }
public IEnumerable<ConnectionInfo> GetRecursiveFavoriteChildList(ContainerInfo container) public IEnumerable<ConnectionInfo> GetRecursiveFavoriteChildList(ContainerInfo container)

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using mRemoteNG.Connection;
using mRemoteNG.Container;
namespace mRemoteNG.Tree
{
public interface IConnectionTreeModel : INotifyCollectionChanged, INotifyPropertyChanged
{
List<ContainerInfo> RootNodes { get; }
void AddRootNode(ContainerInfo rootNode);
void RemoveRootNode(ContainerInfo rootNode);
IReadOnlyList<ConnectionInfo> GetRecursiveChildList();
IEnumerable<ConnectionInfo> GetRecursiveChildList(ContainerInfo container);
void RenameNode(ConnectionInfo connectionInfo, string newName);
void DeleteNode(ConnectionInfo connectionInfo);
IEnumerable<ConnectionInfo> GetRecursiveFavoriteChildList(ContainerInfo node);
}
}

View File

@@ -7,13 +7,13 @@ namespace mRemoteNG.Tree
{ {
public class NodeSearcher public class NodeSearcher
{ {
private readonly ConnectionTreeModel _connectionTreeModel; private readonly IConnectionTreeModel _connectionTreeModel;
private List<ConnectionInfo> Matches { get; set; } private List<ConnectionInfo> Matches { get; set; }
public ConnectionInfo CurrentMatch { get; private set; } public ConnectionInfo CurrentMatch { get; private set; }
public NodeSearcher(ConnectionTreeModel connectionTreeModel) public NodeSearcher(IConnectionTreeModel connectionTreeModel)
{ {
_connectionTreeModel = connectionTreeModel; _connectionTreeModel = connectionTreeModel;
} }

View File

@@ -21,9 +21,7 @@ namespace mRemoteNG.UI.Controls.Adapters
_editorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService; _editorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
if (_editorService == null) return value; if (_editorService == null) return value;
var credentialManager = Runtime.CredentialProviderCatalog; var listBox = new CredentialRecordListBox(Runtime.CredentialService.RepositoryList.GetCredentialRecords());
var listBox = new CredentialRecordListBox(credentialManager.GetCredentialRecords());
listBox.SelectedValueChanged += ListBoxOnSelectedValueChanged; listBox.SelectedValueChanged += ListBoxOnSelectedValueChanged;
_editorService.DropDownControl(listBox); _editorService.DropDownControl(listBox);

View File

@@ -6,6 +6,7 @@ using mRemoteNG.App;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol;
using mRemoteNG.Container; using mRemoteNG.Container;
using mRemoteNG.Security;
using mRemoteNG.Tools; using mRemoteNG.Tools;
using mRemoteNG.Tools.Clipboard; using mRemoteNG.Tools.Clipboard;
using mRemoteNG.Tree; using mRemoteNG.Tree;
@@ -57,7 +58,7 @@ namespace mRemoteNG.UI.Controls
public ConnectionContextMenu(ConnectionTree.ConnectionTree connectionTree) public ConnectionContextMenu(ConnectionTree.ConnectionTree connectionTree)
{ {
_connectionTree = connectionTree; _connectionTree = connectionTree;
_connectionInitiator = new ConnectionInitiator(); _connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
InitializeComponent(); InitializeComponent();
ApplyLanguage(); ApplyLanguage();
EnableShortcutKeys(); EnableShortcutKeys();
@@ -761,8 +762,8 @@ namespace mRemoteNG.UI.Controls
{ {
Windows.Show(WindowType.SSHTransfer); Windows.Show(WindowType.SSHTransfer);
Windows.SshtransferForm.Hostname = _connectionTree.SelectedNode.Hostname; Windows.SshtransferForm.Hostname = _connectionTree.SelectedNode.Hostname;
Windows.SshtransferForm.Username = _connectionTree.SelectedNode.Username; Windows.SshtransferForm.Username = _connectionTree.SelectedNode.CredentialRecord.Username;
Windows.SshtransferForm.Password = _connectionTree.SelectedNode.Password; Windows.SshtransferForm.Password = _connectionTree.SelectedNode.CredentialRecord.Password.ConvertToUnsecureString();
Windows.SshtransferForm.Port = Convert.ToString(_connectionTree.SelectedNode.Port); Windows.SshtransferForm.Port = Convert.ToString(_connectionTree.SelectedNode.Port);
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -15,7 +15,6 @@ using mRemoteNG.Tools.Clipboard;
using mRemoteNG.Tree; using mRemoteNG.Tree;
using mRemoteNG.Tree.ClickHandlers; using mRemoteNG.Tree.ClickHandlers;
using mRemoteNG.Tree.Root; using mRemoteNG.Tree.Root;
// ReSharper disable ArrangeAccessorOwnerBody // ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.UI.Controls.ConnectionTree namespace mRemoteNG.UI.Controls.ConnectionTree
@@ -33,7 +32,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
private bool _nodeInEditMode; private bool _nodeInEditMode;
private bool _allowEdit; private bool _allowEdit;
private ConnectionContextMenu _contextMenu; private ConnectionContextMenu _contextMenu;
private ConnectionTreeModel _connectionTreeModel; private IConnectionTreeModel _connectionTreeModel;
public ConnectionInfo SelectedNode => (ConnectionInfo)SelectedObject; public ConnectionInfo SelectedNode => (ConnectionInfo)SelectedObject;
@@ -49,7 +48,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
public ITreeNodeClickHandler<ConnectionInfo> SingleClickHandler { get; set; } = public ITreeNodeClickHandler<ConnectionInfo> SingleClickHandler { get; set; } =
new TreeNodeCompositeClickHandler(); new TreeNodeCompositeClickHandler();
public ConnectionTreeModel ConnectionTreeModel public IConnectionTreeModel ConnectionTreeModel
{ {
get { return _connectionTreeModel; } get { return _connectionTreeModel; }
set set
@@ -196,7 +195,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
padding; padding;
} }
private void PopulateTreeView(ConnectionTreeModel newModel) private void PopulateTreeView(IConnectionTreeModel newModel)
{ {
SetObjects(newModel.RootNodes); SetObjects(newModel.RootNodes);
RegisterModelUpdateHandlers(newModel); RegisterModelUpdateHandlers(newModel);
@@ -205,14 +204,14 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
AutoResizeColumn(Columns[0]); AutoResizeColumn(Columns[0]);
} }
private void RegisterModelUpdateHandlers(ConnectionTreeModel newModel) private void RegisterModelUpdateHandlers(IConnectionTreeModel newModel)
{ {
_puttySessionsManager.PuttySessionsCollectionChanged += OnPuttySessionsCollectionChanged; _puttySessionsManager.PuttySessionsCollectionChanged += OnPuttySessionsCollectionChanged;
newModel.CollectionChanged += HandleCollectionChanged; newModel.CollectionChanged += HandleCollectionChanged;
newModel.PropertyChanged += HandleCollectionPropertyChanged; newModel.PropertyChanged += HandleCollectionPropertyChanged;
} }
private void UnregisterModelUpdateHandlers(ConnectionTreeModel oldConnectionTreeModel) private void UnregisterModelUpdateHandlers(IConnectionTreeModel oldConnectionTreeModel)
{ {
_puttySessionsManager.PuttySessionsCollectionChanged -= OnPuttySessionsCollectionChanged; _puttySessionsManager.PuttySessionsCollectionChanged -= OnPuttySessionsCollectionChanged;
@@ -261,12 +260,13 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
public RootNodeInfo GetRootConnectionNode() public RootNodeInfo GetRootConnectionNode()
{ {
return (RootNodeInfo)ConnectionTreeModel.RootNodes.First(item => item is RootNodeInfo); return (RootNodeInfo)ConnectionTreeModel.RootNodes.FirstOrDefault(item => item is RootNodeInfo);
} }
public void Invoke(Action action) public void Invoke(Action action)
{ {
Invoke((Delegate)action); if (Created)
Invoke((Delegate)action);
} }
public void InvokeExpand(object model) public void InvokeExpand(object model)
@@ -433,7 +433,11 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
ResetColumnFiltering(); ResetColumnFiltering();
} }
RefreshObject(sender); if (sender is IConnectionTree)
RebuildAll(true);
else
RefreshObject(sender);
AutoResizeColumn(Columns[0]); AutoResizeColumn(Columns[0]);
// turn filtering back on // turn filtering back on

View File

@@ -7,7 +7,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
{ {
public interface IConnectionTree public interface IConnectionTree
{ {
ConnectionTreeModel ConnectionTreeModel { get; set; } IConnectionTreeModel ConnectionTreeModel { get; set; }
ConnectionInfo SelectedNode { get; } ConnectionInfo SelectedNode { get; }

View File

@@ -6,7 +6,17 @@ namespace mRemoteNG.UI.Controls
{ {
public partial class CredentialRecordComboBox : ComboBox public partial class CredentialRecordComboBox : ComboBox
{ {
public IEnumerable<ICredentialRecord> CredentialRecords { get; set; } private IEnumerable<ICredentialRecord> _credentialRecords;
public IEnumerable<ICredentialRecord> CredentialRecords
{
get => _credentialRecords;
set
{
_credentialRecords = value;
PopulateItems(_credentialRecords);
}
}
public CredentialRecordComboBox() public CredentialRecordComboBox()
{ {

View File

@@ -8,7 +8,7 @@ namespace mRemoteNG.UI.Controls
public partial class CredentialRecordListBox : ListBox public partial class CredentialRecordListBox : ListBox
{ {
public new ICredentialRecord SelectedItem => (ICredentialRecord)base.SelectedItem; public new ICredentialRecord SelectedItem => (ICredentialRecord)base.SelectedItem;
public ICredentialRecord NoneSelection { get; } = new CredentialRecord {Title = $"--{Language.None}--"}; public ICredentialRecord NoneSelection { get; } = new NullCredentialRecord();
public ICredentialRecord AddNewSelection { get; } = new CredentialRecord {Title = $"--{Language.Add}--"}; public ICredentialRecord AddNewSelection { get; } = new CredentialRecord {Title = $"--{Language.Add}--"};
public CredentialRecordListBox(IEnumerable<ICredentialRecord> listOfCredentialRecords) public CredentialRecordListBox(IEnumerable<ICredentialRecord> listOfCredentialRecords)

View File

@@ -46,6 +46,7 @@
this.objectListView1.AllColumns.Add(this.olvColumnCredentialId); this.objectListView1.AllColumns.Add(this.olvColumnCredentialId);
this.objectListView1.AllColumns.Add(this.olvColumnRepositoryTitle); this.objectListView1.AllColumns.Add(this.olvColumnRepositoryTitle);
this.objectListView1.AllColumns.Add(this.olvColumnRepositorySource); this.objectListView1.AllColumns.Add(this.olvColumnRepositorySource);
this.objectListView1.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.objectListView1.CellEditUseWholeCell = false; this.objectListView1.CellEditUseWholeCell = false;
this.objectListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { this.objectListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.olvColumnTitle, this.olvColumnTitle,
@@ -108,7 +109,6 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.Controls.Add(this.objectListView1); this.Controls.Add(this.objectListView1);
this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.Name = "CredentialRecordListView"; this.Name = "CredentialRecordListView";
this.Size = new System.Drawing.Size(367, 204); this.Size = new System.Drawing.Size(367, 204);
((System.ComponentModel.ISupportInitialize)(this.objectListView1)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.objectListView1)).EndInit();

View File

@@ -0,0 +1,59 @@
using System.Drawing;
using System.Windows.Forms;
namespace mRemoteNG.UI.Controls
{
public class MrngToolStripSplitButton : System.Windows.Forms.ToolStripSplitButton
{
public new ToolStripDropDown DropDown
{
get { return base.DropDown; }
set
{
if (base.DropDown != value)
{
base.DropDown = value;
base.DropDown.Closing += DropDown_Closing;
}
}
}
private void DropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e)
{
if (e.CloseReason != ToolStripDropDownCloseReason.AppClicked)
{
return;
}
Rectangle dropDownButtonBoundsClient = DropDownButtonBounds; // Relative to the ToolStripSplitButton
dropDownButtonBoundsClient.Offset(Bounds.Location); // Relative to the parent of the ToolStripSplitButton
Rectangle dropDownButtonBoundsScreen =
GetCurrentParent().RectangleToScreen(dropDownButtonBoundsClient); // Relative to the screen
if (dropDownButtonBoundsScreen.Contains(Control.MousePosition))
{
e.Cancel = true;
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
_dropDownVisibleOnMouseDown = DropDown.Visible;
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (_dropDownVisibleOnMouseDown)
{
DropDown.Close();
}
else
{
base.OnMouseUp(e);
}
}
private bool _dropDownVisibleOnMouseDown;
}
}

View File

@@ -44,6 +44,10 @@ namespace mRemoteNG.UI.Controls
public NewPasswordWithVerification() public NewPasswordWithVerification()
{ {
InitializeComponent(); InitializeComponent();
ApplyLanguage();
var display = new DisplayProperties();
secureTextBox1.Width = display.ScaleWidth(Width);
secureTextBox2.Width = display.ScaleWidth(Width);
secureTextBox1.TextChanged += OnSecureTextBoxTextChanged; secureTextBox1.TextChanged += OnSecureTextBoxTextChanged;
secureTextBox2.TextChanged += OnSecureTextBoxTextChanged; secureTextBox2.TextChanged += OnSecureTextBoxTextChanged;
} }
@@ -55,6 +59,14 @@ namespace mRemoteNG.UI.Controls
secureTextBox2.Text = text; secureTextBox2.Text = text;
} }
private void ApplyLanguage()
{
//TODO: RESX
//labelFirstPasswordBox.Text = Language.strNewPassword;
//labelSecondPasswordBox.Text = Language.strVerifyPassword;
//labelPasswordsDontMatch.Text = Language.strPasswordsDontMatch;
}
private bool Verify() private bool Verify()
{ {
return secureTextBox1.SecString.Length == secureTextBox2.SecString.Length && return secureTextBox1.SecString.Length == secureTextBox2.SecString.Length &&
@@ -90,16 +102,28 @@ namespace mRemoteNG.UI.Controls
{ {
PasswordsMatch = false; PasswordsMatch = false;
SecureString = null; SecureString = null;
RaiseNotVerifiedEvent();
} }
TogglePasswordMatchIndicator(PasswordsMatch); TogglePasswordMatchIndicator(PasswordsMatch);
} }
/// <summary>
/// Raised when the two given passwords go from not matching to matching (verified).
/// </summary>
public event EventHandler Verified; public event EventHandler Verified;
private void RaiseVerifiedEvent() private void RaiseVerifiedEvent()
{ {
Verified?.Invoke(this, EventArgs.Empty); Verified?.Invoke(this, EventArgs.Empty);
} }
/// <summary>
/// Raised when the two given passwords go from matching to not matching (not verified).
/// </summary>
public event EventHandler NotVerified;
private void RaiseNotVerifiedEvent()
{
NotVerified?.Invoke(this, EventArgs.Empty);
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More