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

View File

@@ -10,7 +10,7 @@ using mRemoteNG.Tools;
namespace mRemoteNG.App
{
public static class Import
public static class Import
{
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.Connection;
using mRemoteNG.Credential;
using mRemoteNG.Tools;
using mRemoteNG.Properties;
namespace mRemoteNG.App.Initialization
{
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 &&
!File.Exists(Runtime.ConnectionsService.GetStartupConnectionFileName()))
Runtime.ConnectionsService.NewConnectionsFile(Runtime.ConnectionsService
.GetStartupConnectionFileName());
if (Settings.Default.FirstStart && !Settings.Default.LoadConsFromCustomLocation &&
!File.Exists(connectionsService.GetStartupConnectionFileName()))
connectionsService.NewConnectionsFile(connectionsService.GetStartupConnectionFileName());
credentialService.LoadRepositoryList();
LoadDefaultConnectionCredentials(credentialService);
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.Connection;
using mRemoteNG.Credential;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.Messages;
using mRemoteNG.Security;
using mRemoteNG.Tools;
@@ -36,7 +35,7 @@ namespace mRemoteNG.App
/// <summary>
/// Feature flag to enable the credential manager feature
/// </summary>
public static bool UseCredentialManager => false;
public static bool UseCredentialManager => true;
public static WindowList WindowList { get; set; }
public static MessageCollector MessageCollector { get; } = new MessageCollector();
@@ -46,10 +45,9 @@ namespace mRemoteNG.App
public static SecureString EncryptionKey { get; set; } =
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; } =
new ConnectionsService(PuttySessionsManager.Instance);
public static ConnectionsService ConnectionsService { get; } = new ConnectionsService(PuttySessionsManager.Instance, CredentialService);
#region Connections Loading/Saving
@@ -96,7 +94,7 @@ namespace mRemoteNG.App
connectionFileName = ConnectionsService.GetStartupConnectionFileName();
}
ConnectionsService.LoadConnections(Settings.Default.UseSQLServer, false, connectionFileName);
ConnectionsService.LoadConnections(Settings.Default.UseSQLServer, connectionFileName);
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.Collections.Generic;
using mRemoteNG.Connection;
using mRemoteNG.Tools;
using mRemoteNG.Tree;
@@ -10,7 +12,7 @@ namespace mRemoteNG.Config.Connections
/// The previous <see cref="ConnectionTreeModel"/> that is being
/// unloaded.
/// </summary>
public Optional<ConnectionTreeModel> PreviousConnectionTreeModel { get; }
public List<ConnectionInfo> RemovedConnections { get; }
/// <summary>
/// True if the previous <see cref="ConnectionTreeModel"/> was loaded from
@@ -21,7 +23,7 @@ namespace mRemoteNG.Config.Connections
/// <summary>
/// The new <see cref="ConnectionTreeModel"/> that is being loaded.
/// </summary>
public ConnectionTreeModel NewConnectionTreeModel { get; }
public List<ConnectionInfo> AddedConnections { get; }
/// <summary>
/// True if the new <see cref="ConnectionTreeModel"/> was loaded from
@@ -36,24 +38,18 @@ namespace mRemoteNG.Config.Connections
/// </summary>
public string NewSourcePath { get; }
public ConnectionsLoadedEventArgs(Optional<ConnectionTreeModel> previousTreeModelModel,
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));
public IConnectionTreeModel NewConnectionTreeModel { get; set; } = new ConnectionTreeModel();
PreviousConnectionTreeModel = previousTreeModelModel;
public ConnectionsLoadedEventArgs(
List<ConnectionInfo> removedConnections, List<ConnectionInfo> addedConnections,
bool previousSourceWasDatabase, bool newSourceIsDatabase,
string newSourcePath)
{
RemovedConnections = removedConnections.ThrowIfNull(nameof(removedConnections));
PreviousSourceWasDatabase = previousSourceWasDatabase;
NewConnectionTreeModel = newTreeModelModel;
AddedConnections = addedConnections.ThrowIfNull(nameof(addedConnections));
NewSourceIsDatabase = newSourceIsDatabase;
NewSourcePath = newSourcePath;
NewSourcePath = newSourcePath.ThrowIfNull(nameof(newSourcePath));
}
}
}
}

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
using mRemoteNG.Tree;
using mRemoteNG.Config.Serializers;
namespace mRemoteNG.Config.Connections
{
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)
{
Runtime.ConnectionsService.LoadConnections(true, false, "");
Runtime.ConnectionsService.LoadConnections(true, "");
args.Handled = true;
}

View File

@@ -1,36 +1,26 @@
using System;
using System.Collections.Specialized;
using System.Collections.Specialized;
using System.ComponentModel;
using mRemoteNG.Connection;
using mRemoteNG.UI.Forms;
using mRemoteNG.Tools;
namespace mRemoteNG.Config.Connections
{
public class SaveConnectionsOnEdit
{
private readonly ConnectionsService _connectionsService;
private IConnectionsService _connectionsService;
public SaveConnectionsOnEdit(ConnectionsService connectionsService)
public void Subscribe(IConnectionsService connectionsService)
{
if (connectionsService == null)
throw new ArgumentNullException(nameof(connectionsService));
_connectionsService = connectionsService;
connectionsService.ConnectionsLoaded += ConnectionsServiceOnConnectionsLoaded;
_connectionsService = connectionsService.ThrowIfNull(nameof(connectionsService));
connectionsService.ConnectionTreeModel.CollectionChanged += ConnectionTreeModelOnCollectionChanged;
connectionsService.ConnectionTreeModel.PropertyChanged += ConnectionTreeModelOnPropertyChanged;
}
private void ConnectionsServiceOnConnectionsLoaded(object sender,
ConnectionsLoadedEventArgs connectionsLoadedEventArgs)
public void Unsubscribe()
{
connectionsLoadedEventArgs.NewConnectionTreeModel.CollectionChanged +=
ConnectionTreeModelOnCollectionChanged;
connectionsLoadedEventArgs.NewConnectionTreeModel.PropertyChanged += ConnectionTreeModelOnPropertyChanged;
foreach (var oldTree in connectionsLoadedEventArgs.PreviousConnectionTreeModel)
{
oldTree.CollectionChanged -= ConnectionTreeModelOnCollectionChanged;
oldTree.PropertyChanged -= ConnectionTreeModelOnPropertyChanged;
}
_connectionsService.ConnectionTreeModel.CollectionChanged -= ConnectionTreeModelOnCollectionChanged;
_connectionsService.ConnectionTreeModel.PropertyChanged -= ConnectionTreeModelOnPropertyChanged;
_connectionsService = null;
}
private void ConnectionTreeModelOnPropertyChanged(object sender,
@@ -50,10 +40,8 @@ namespace mRemoteNG.Config.Connections
{
if (!Properties.Settings.Default.SaveConnectionsAfterEveryEdit)
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.SymmetricEncryption;
using mRemoteNG.Tools;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections
@@ -36,7 +35,7 @@ namespace mRemoteNG.Config.Connections
_dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
}
public ConnectionTreeModel Load()
public SerializationResult Load()
{
var connector = DatabaseConnectorFactory.DatabaseConnectorFromSettings();
var dataProvider = new SqlDataProvider(connector);
@@ -54,9 +53,9 @@ namespace mRemoteNG.Config.Connections
databaseVersionVerifier.VerifyDatabaseVersion(metaData.ConfVersion);
var dataTable = dataProvider.Load();
var deserializer = new DataTableDeserializer(cryptoProvider, decryptionKey.First());
var connectionTree = deserializer.Deserialize(dataTable);
ApplyLocalConnectionProperties(connectionTree.RootNodes.First(i => i is RootNodeInfo));
return connectionTree;
var serializationResult = deserializer.Deserialize(dataTable);
ApplyLocalConnectionProperties(serializationResult.ConnectionRecords.OfType<RootNodeInfo>().First());
return serializationResult;
}
private Optional<SecureString> GetDecryptionKey(SqlConnectionListMetaData metaData)

View File

@@ -20,7 +20,7 @@ using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections
{
public class SqlConnectionsSaver : ISaver<ConnectionTreeModel>
public class SqlConnectionsSaver : ISaver<IConnectionTreeModel>
{
private readonly SaveFilter _saveFilter;
private readonly ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> _localPropertiesSerializer;
@@ -38,7 +38,7 @@ namespace mRemoteNG.Config.Connections
_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();

View File

@@ -1,18 +1,25 @@
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Tools;
using mRemoteNG.Tree;
using System;
using System.IO;
using System.Security;
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
{
public class XmlConnectionsLoader : IConnectionsLoader
{
private readonly string _credentialFilePath = Path.Combine(CredentialsFileInfo.CredentialsPath, CredentialsFileInfo.CredentialsFile);
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))
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");
_connectionFilePath = connectionFilePath;
_connectionsService = connectionsService.ThrowIfNull(nameof(connectionsService));
_credentialService = credentialService.ThrowIfNull(nameof(credentialService));
}
public ConnectionTreeModel Load()
public SerializationResult Load()
{
var dataProvider = new FileDataProvider(_connectionFilePath);
var xmlString = dataProvider.Load();
var deserializer = new XmlConnectionsDeserializer(PromptForPassword);
return deserializer.Deserialize(xmlString);
var deserializer = new CredentialManagerUpgradeForm
{
ConnectionFilePath = _connectionFilePath,
NewCredentialRepoPath = _credentialFilePath,
ConnectionsService = _connectionsService,
CredentialService = _credentialService,
ConnectionDeserializer = new XmlConnectionsDeserializer(PromptForPassword)
};
var serializationResult = deserializer.Deserialize(xmlString);
return serializationResult;
}
private Optional<SecureString> PromptForPassword()

View File

@@ -10,7 +10,7 @@ using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections
{
public class XmlConnectionsSaver : ISaver<ConnectionTreeModel>
public class XmlConnectionsSaver : ISaver<IConnectionTreeModel>
{
private readonly string _connectionFileName;
private readonly SaveFilter _saveFilter;
@@ -26,7 +26,7 @@ namespace mRemoteNG.Config.Connections
_saveFilter = saveFilter;
}
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
public void Save(IConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
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 System.Linq;
using mRemoteNG.App;
using mRemoteNG.App;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Csv;
using mRemoteNG.Container;
using mRemoteNG.Messages;
using System.IO;
using System.Linq;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.Config.Import
{
@@ -25,11 +26,18 @@ namespace mRemoteNG.Config.Import
var dataProvider = new FileDataProvider(filePath);
var xmlString = dataProvider.Load();
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)};
rootImportContainer.AddChildRange(connectionTreeModel.RootNodes.First().Children.ToArray());
rootImportContainer.AddChildRange(serializationResult.ConnectionRecords);
destinationContainer.AddChild(rootImportContainer);
}
}
}
}

View File

@@ -1,5 +1,4 @@
using System.IO;
using System.Linq;
using mRemoteNG.App;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
@@ -27,10 +26,10 @@ namespace mRemoteNG.Config.Import
var dataProvider = new FileDataProvider(fileName);
var xmlString = dataProvider.Load();
var xmlConnectionsDeserializer = new XmlConnectionsDeserializer();
var connectionTreeModel = xmlConnectionsDeserializer.Deserialize(xmlString, true);
var serializationResult = xmlConnectionsDeserializer.Deserialize(xmlString, true);
var rootImportContainer = new ContainerInfo {Name = Path.GetFileNameWithoutExtension(fileName)};
rootImportContainer.AddChildRange(connectionTreeModel.RootNodes.First().Children.ToArray());
rootImportContainer.AddChildRange(serializationResult.ConnectionRecords);
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.Container;
@@ -14,12 +13,9 @@ namespace mRemoteNG.Config.Import
var xmlContent = dataProvider.Load();
var deserializer = new PuttyConnectionManagerDeserializer();
var connectionTreeModel = deserializer.Deserialize(xmlContent);
var serializationResult = deserializer.Deserialize(xmlContent);
var importedRootNode = connectionTreeModel.RootNodes.First();
if (importedRootNode == null) return;
var childrenToAdd = importedRootNode.Children.ToArray();
destinationContainer.AddChildRange(childrenToAdd);
destinationContainer.AddChildRange(serializationResult.ConnectionRecords);
}
}
}

View File

@@ -15,9 +15,9 @@ namespace mRemoteNG.Config.Import
var content = dataProvider.Load();
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;
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.Container;
@@ -14,12 +13,9 @@ namespace mRemoteNG.Config.Import
var fileContent = dataProvider.Load();
var deserializer = new RemoteDesktopConnectionManagerDeserializer();
var connectionTreeModel = deserializer.Deserialize(fileContent);
var serializationResult = deserializer.Deserialize(fileContent);
var importedRootNode = connectionTreeModel.RootNodes.First();
if (importedRootNode == null) return;
var childrenToAdd = importedRootNode.Children.ToArray();
destinationContainer.AddChildRange(childrenToAdd);
destinationContainer.AddChildRange(serializationResult.ConnectionRecords);
}
}
}

View File

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

View File

@@ -1,20 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using mRemoteNG.Config.Serializers.CredentialSerializer;
using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.Http;
using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Connection.Protocol.VNC;
using mRemoteNG.Container;
using mRemoteNG.Security;
using mRemoteNG.Tools;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
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 csvHeaders = new List<string>();
@@ -29,26 +32,39 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
else
{
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 connectionTreeModel = new ConnectionTreeModel();
connectionTreeModel.AddRootNode(root);
return connectionTreeModel;
var harvestedCredentials = new CredentialHarvester()
.Harvest(new HarvestConfig<string[]>
{
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)
{
// no parent mapped, add to root
if (string.IsNullOrEmpty(node.Value))
{
root.AddChild(node.Key);
root.Add(node.Key);
continue;
}
@@ -64,7 +80,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
}
else
{
root.AddChild(node.Key);
root.Add(node.Key);
}
}
@@ -85,53 +101,25 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
? new ConnectionInfo(nodeId)
: new ContainerInfo(nodeId);
connectionRecord.Name = headers.Contains("Name")
? connectionCsv[headers.IndexOf("Name")]
: "";
connectionRecord.Name = headers.Contains("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")
? connectionCsv[headers.IndexOf("Description")]
: "";
var hasCredRecordId = Guid.TryParse(
headers.Contains("CredentialRecordId") ? connectionCsv[headers.IndexOf("CredentialRecordId")] : "",
out var credRecordId);
connectionRecord.CredentialRecordId = hasCredRecordId ? credRecordId : Optional<Guid>.Empty;
connectionRecord.Icon = headers.Contains("Icon")
? connectionCsv[headers.IndexOf("Icon")]
: "";
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")]
: "";
// TODO: harvest
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.PuttySession =
headers.Contains("PuttySession") ? connectionCsv[headers.IndexOf("PuttySession")] : "";
connectionRecord.LoadBalanceInfo = headers.Contains("LoadBalanceInfo")
? connectionCsv[headers.IndexOf("LoadBalanceInfo")]
: "";
@@ -186,6 +174,12 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
? 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 (Enum.TryParse(connectionCsv[headers.IndexOf("Protocol")], out ProtocolType protocolType))
@@ -825,9 +819,16 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
connectionRecord.Inheritance.RdpVersion = value;
}
if (headers.Contains("InheritCredentialRecord"))
{
if (bool.TryParse(connectionCsv[headers.IndexOf("InheritCredentialRecord")], out bool value))
connectionRecord.Inheritance.CredentialId = value;
}
#endregion
return connectionRecord;
}
}
}
}

View File

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

View File

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

View File

@@ -105,9 +105,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataTable.Columns.Add("Description", typeof(string));
dataTable.Columns.Add("Icon", 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("Port", typeof(int));
dataTable.Columns.Add("Protocol", typeof(string));
@@ -502,16 +499,10 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataRow["ParentID"] = connectionInfo.Parent?.ConstantID ?? "";
dataRow["PositionID"] = _currentNodeIndex;
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["Icon"] = connectionInfo.Icon;
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["VmId"] = connectionInfo.VmId;
dataRow["Protocol"] = connectionInfo.Protocol;
@@ -549,7 +540,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataRow["SoundQuality"] = connectionInfo.SoundQuality;
dataRow["RedirectAudioCapture"] = connectionInfo.RedirectAudioCapture;
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["PostExtApp"] = connectionInfo.PostExtApp;
dataRow["MacAddress"] = connectionInfo.MacAddress;
@@ -592,7 +582,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataRow["InheritDomain"] = connectionInfo.Inheritance.Domain;
dataRow["InheritIcon"] = connectionInfo.Inheritance.Icon;
dataRow["InheritPanel"] = connectionInfo.Inheritance.Panel;
dataRow["InheritPassword"] = connectionInfo.Inheritance.Password;
dataRow["InheritPort"] = connectionInfo.Inheritance.Port;
dataRow["InheritProtocol"] = connectionInfo.Inheritance.Protocol;
dataRow["InheritSSHTunnelConnectionName"] = connectionInfo.Inheritance.SSHTunnelConnectionName;
@@ -660,7 +649,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
dataRow["InheritDomain"] = false;
dataRow["InheritIcon"] = false;
dataRow["InheritPanel"] = false;
dataRow["InheritPassword"] = false;
dataRow["InheritPort"] = false;
dataRow["InheritProtocol"] = false;
dataRow["InheritSSHTunnelConnectionName"] = false;

View File

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

View File

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

View File

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

View File

@@ -9,29 +9,38 @@ namespace mRemoteNG.Config.Serializers.CredentialProviderSerializer
{
public class CredentialRepositoryListDeserializer
{
private readonly ISecureSerializer<IEnumerable<ICredentialRecord>, string> _serializer;
private readonly ISecureDeserializer<string, IEnumerable<ICredentialRecord>> _deserializer;
public CredentialRepositoryListDeserializer(
ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer,
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer)
public IEnumerable<ICredentialRepository> Deserialize(string xml, IEnumerable<ICredentialRepositoryFactory> factories)
{
if (serializer == null)
throw new ArgumentNullException(nameof(serializer));
if (deserializer == null)
throw new ArgumentNullException(nameof(deserializer));
if (string.IsNullOrEmpty(xml))
return new ICredentialRepository[0];
_serializer = serializer;
_deserializer = deserializer;
}
public IEnumerable<ICredentialRepository> Deserialize(string xml)
{
if (string.IsNullOrEmpty(xml)) return new ICredentialRepository[0];
var xdoc = XDocument.Parse(xml);
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 System.IO;
using System.Xml;
using mRemoteNG.Connection;
using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Container;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Xml;
using mRemoteNG.Credential;
using mRemoteNG.Security;
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 root = new RootNodeInfo(RootNodeType.Connection);
connectionTreeModel.AddRootNode(root);
var result = new SerializationResult(new List<ConnectionInfo>(), new ConnectionToCredentialMap());
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(puttycmConnectionsXml);
var configurationNode = xmlDocument.SelectSingleNode("/configuration");
var rootNodes = configurationNode?.SelectNodes("./root");
if (rootNodes == null) return connectionTreeModel;
foreach (XmlNode rootNode in rootNodes)
var rootXmlNode = configurationNode?.SelectSingleNode("./root");
if (rootXmlNode == null)
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);
var newContainer = ImportContainer(xmlNode, parentContainer);
var newContainer = ReadContainerProperties(xmlNode);
var childNodes = xmlNode.SelectNodes("./*");
if (childNodes == null) return;
if (childNodes == null)
return newContainer;
foreach (XmlNode childNode in childNodes)
{
switch (childNode.Name)
{
case "container":
ImportRootOrContainer(childNode, newContainer);
newContainer.AddChild(ImportRecursive(childNode, credentialMap));
break;
case "connection":
ImportConnection(childNode, newContainer);
newContainer.AddChild(ImportConnection(childNode, credentialMap));
break;
default:
throw (new FileFormatException($"Unrecognized child node ({childNode.Name})."));
throw new FileFormatException($"Unrecognized child node ({childNode.Name}).");
}
}
return newContainer;
}
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
{
Name = containerNode.Attributes?["name"].Value,
IsExpanded = bool.Parse(containerNode.Attributes?["expanded"].InnerText ?? "false")
};
parentContainer.AddChild(containerInfo);
return containerInfo;
}
private void ImportConnection(XmlNode connectionNode, ContainerInfo parentContainer)
private ConnectionInfo ImportConnection(XmlNode connectionNode, ConnectionToCredentialMap credentialMap)
{
var connectionNodeType = connectionNode.Attributes?["type"].Value;
if (string.Compare(connectionNodeType, "PuTTY", StringComparison.OrdinalIgnoreCase) != 0)
throw (new FileFormatException($"Unrecognized connection node type ({connectionNodeType})."));
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)
@@ -129,9 +141,6 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
// ./commandline
connectionInfo.Description = connectionInfoNode.SelectSingleNode("./description")?.InnerText;
var loginNode = xmlNode.SelectSingleNode("./login");
connectionInfo.Username = loginNode?.SelectSingleNode("login")?.InnerText;
connectionInfo.Password = loginNode?.SelectSingleNode("password")?.InnerText;
// ./prompt
// ./timeout/connectiontimeout
@@ -151,5 +160,19 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
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 mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
using System.Collections.Generic;
using mRemoteNG.Credential;
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
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 username = "";
var domain = "";
foreach (var line in rdcFileContent.Split(Environment.NewLine.ToCharArray()))
{
var parts = line.Split(new[] { ':' }, 3);
@@ -24,21 +24,42 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
continue;
}
var key = parts[0].Trim();
var propertyName = parts[0].Trim().ToLowerInvariant();
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)
{
switch (key.ToLower())
switch (key)
{
case "full address":
var uri = new Uri("dummyscheme" + Uri.SchemeDelimiter + value);
@@ -50,12 +71,6 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
case "server port":
connectionInfo.Port = Convert.ToInt32(value);
break;
case "username":
connectionInfo.Username = value;
break;
case "domain":
connectionInfo.Domain = value;
break;
case "session bpp":
switch (value)
{

View File

@@ -1,40 +1,44 @@
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Container;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Container;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
using mRemoteNG.Credential;
using mRemoteNG.Security;
using mRemoteNG.Tools;
namespace mRemoteNG.Config.Serializers.MiscSerializers
{
public class RemoteDesktopConnectionManagerDeserializer : IDeserializer<string, ConnectionTreeModel>
public class RemoteDesktopConnectionManagerDeserializer
{
private static int _schemaVersion; /* 1 = RDCMan v2.2
3 = RDCMan v2.7 */
// 1 = RDCMan v2.2
// 3 = RDCMan v2.7
private static int _schemaVersion;
public ConnectionTreeModel Deserialize(string rdcmConnectionsXml)
public SerializationResult Deserialize(string rdcmConnectionsXml)
{
var connectionTreeModel = new ConnectionTreeModel();
var root = new RootNodeInfo(RootNodeType.Connection);
var serializationResult = new SerializationResult(new List<ConnectionInfo>(), new ConnectionToCredentialMap());
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(rdcmConnectionsXml);
var rdcManNode = xmlDocument.SelectSingleNode("/RDCMan");
VerifySchemaVersion(rdcManNode);
VerifyFileVersion(rdcManNode);
var fileNode = rdcManNode?.SelectSingleNode("./file");
ImportFileOrGroup(fileNode, root);
var importedItem = ImportFileOrGroup(fileNode, serializationResult.ConnectionToCredentialMap);
connectionTreeModel.AddRootNode(root);
return connectionTreeModel;
serializationResult.ConnectionRecords.Add(importedItem);
return serializationResult;
}
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");
if (childNodes == null) return;
if (childNodes == null)
return newContainer;
foreach (XmlNode childNode in childNodes)
{
ConnectionInfo newChild = null;
// ReSharper disable once SwitchStatementMissingSomeCases
switch (childNode.Name)
{
case "group":
ImportFileOrGroup(childNode, newContainer);
newChild = ImportFileOrGroup(childNode, credentialMap);
break;
case "server":
ImportServer(childNode, newContainer);
newChild = ConnectionInfoFromXml(childNode);
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)
{
@@ -120,16 +142,9 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
newContainer.Name = containerPropertiesNode?.SelectSingleNode("./name")?.InnerText ?? Language.NewFolder;
if (bool.TryParse(containerPropertiesNode?.SelectSingleNode("./expanded")?.InnerText, out var expanded))
newContainer.IsExpanded = expanded;
parentContainer.AddChild(newContainer);
return newContainer;
}
private static void ImportServer(XmlNode serverNode, ContainerInfo parentContainer)
{
var newConnectionInfo = ConnectionInfoFromXml(serverNode);
parentContainer.AddChild(newConnectionInfo);
}
private static ConnectionInfo ConnectionInfoFromXml(XmlNode xmlNode)
{
var connectionInfo = new ConnectionInfo {Protocol = ProtocolType.RDP};
@@ -152,25 +167,7 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
connectionInfo.Description = propertiesNode?.SelectSingleNode("./comment")?.InnerText ?? string.Empty;
var logonCredentialsNode = xmlNode.SelectSingleNode("./logonCredentials");
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
if (logonCredentialsNode?.Attributes?["inherit"]?.Value != "None")
{
connectionInfo.Inheritance.Username = true;
connectionInfo.Inheritance.Password = true;
@@ -204,9 +201,9 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
connectionInfo.RDGatewayUsername = gatewaySettingsNode.SelectSingleNode("./userName")?.InnerText ?? string.Empty;
var passwordNode = gatewaySettingsNode.SelectSingleNode("./password");
connectionInfo.RDGatewayPassword = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
? passwordNode.InnerText
: DecryptRdcManPassword(passwordNode?.InnerText);
connectionInfo.RDGatewayPassword = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
? passwordNode.InnerText
: DecryptRdcManPassword(passwordNode?.InnerText).ConvertToUnsecureString();
connectionInfo.RDGatewayDomain = gatewaySettingsNode.SelectSingleNode("./domain")?.InnerText ?? string.Empty;
// ./logonMethod
@@ -348,22 +345,45 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
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))
return string.Empty;
return new SecureString();
try
{
var plaintextData = ProtectedData.Unprotect(Convert.FromBase64String(ciphertext), new byte[] { },
DataProtectionScope.LocalMachine);
var charArray = Encoding.Unicode.GetChars(plaintextData);
return new string(charArray);
return Encoding.Unicode.GetString(plaintextData).ConvertToSecureString();
}
catch (Exception /*ex*/)
{
//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.Drawing.Design;
using System.Linq;
using mRemoteNG.App;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.Http;
using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Connection.Protocol.VNC;
using mRemoteNG.Credential;
using mRemoteNG.Properties;
using mRemoteNG.Tools;
using mRemoteNG.Tools.Attributes;
using mRemoteNG.UI.Controls.Adapters;
namespace mRemoteNG.Connection
@@ -21,6 +27,7 @@ namespace mRemoteNG.Connection
private string _panel;
private string _hostname;
private Optional<Guid> _credentialRecordId = new Optional<Guid>();
private string _username = "";
private string _password = "";
private string _domain = "";
@@ -159,6 +166,7 @@ namespace mRemoteNG.Connection
set => SetField(ref _port, value, "Port");
}
[Browsable(false)]
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Username)),
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionUsername)),
@@ -169,6 +177,7 @@ namespace mRemoteNG.Connection
set => SetField(ref _username, Settings.Default.DoNotTrimUsername ? value : value?.Trim(), "Username");
}
[Browsable(false)]
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Password)),
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionPassword)),
@@ -180,6 +189,7 @@ namespace mRemoteNG.Connection
set => SetField(ref _password, value, "Password");
}
[Browsable(false)]
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Domain)),
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionDomain)),
@@ -210,6 +220,26 @@ namespace mRemoteNG.Connection
get => GetPropertyValue("SSHTunnelConnectionName", _sshTunnelConnectionName).Trim();
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
#region Protocol

View File

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

View File

@@ -48,7 +48,13 @@ namespace mRemoteNG.Connection
#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.LocalizedDisplayNameInherit(nameof(Language.Username)),

View File

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

View File

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

View File

@@ -67,8 +67,11 @@ namespace mRemoteNG.Connection
throw new SettingsPropertyNotFoundException($"No property with name '{expectedPropertyName}' found.");
// ensure value is of correct type
var value = Convert.ChangeType(property.GetValue(Instance, null),
propertyFromDestination.PropertyType);
var value = property.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);
}

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;
}
var argParser = new ExternalToolArgumentParser(_externalTool.ConnectionInfo);
var argParser = new ExternalToolArgumentParser(_externalTool.ConnectionInfo, Runtime.CredentialService);
_process = new Process
{
StartInfo =

View File

@@ -1,15 +1,15 @@
using mRemoteNG.App;
using mRemoteNG.Messages;
using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Tools;
using mRemoteNG.Tools.Cmdline;
using mRemoteNG.UI;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using mRemoteNG.Properties;
using mRemoteNG.Security;
// ReSharper disable ArrangeAccessorOwnerBody
@@ -78,45 +78,20 @@ namespace mRemoteNG.Connection.Protocol
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);
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))
{
arguments.Add("-l", username);
@@ -149,11 +124,11 @@ namespace mRemoteNG.Connection.Protocol
PuttyProcess.Exited += ProcessExited;
PuttyProcess.Start();
PuttyProcess.WaitForInputIdle(Settings.Default.MaxPuttyWaitTime * 1000);
PuttyProcess.WaitForInputIdle(Properties.Settings.Default.MaxPuttyWaitTime * 1000);
var startTicks = Environment.TickCount;
while (PuttyHandle.ToInt32() == 0 &
Environment.TickCount < startTicks + Settings.Default.MaxPuttyWaitTime * 1000)
Environment.TickCount < startTicks + Properties.Settings.Default.MaxPuttyWaitTime * 1000)
{
if (_isPuttyNg)
{

View File

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

View File

@@ -3,6 +3,7 @@ using System.Threading;
using System.ComponentModel;
using System.Net.Sockets;
using mRemoteNG.App;
using mRemoteNG.Security;
using mRemoteNG.Tools;
using mRemoteNG.UI.Forms;
@@ -13,6 +14,8 @@ namespace mRemoteNG.Connection.Protocol.VNC
{
public class ProtocolVNC : ProtocolBase, ISupportsViewOnly
{
private ConnectionInfo Info;
#region Properties
public bool SmartSize
@@ -188,7 +191,7 @@ namespace mRemoteNG.Connection.Protocol.VNC
_vnc.ConnectComplete += VNCEvent_Connected;
_vnc.ConnectionLost += VNCEvent_Disconnected;
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;
}
@@ -268,7 +271,7 @@ namespace mRemoteNG.Connection.Protocol.VNC
private string VNCEvent_Authenticate()
{
return _info.Password;
return Info.CredentialRecord.Password.ConvertToUnsecureString();
}
#endregion

View File

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

View File

@@ -1,4 +1,5 @@
using mRemoteNG.Connection.Protocol;
using mRemoteNG.App;
using mRemoteNG.Connection.Protocol;
namespace mRemoteNG.Connection
{
@@ -16,7 +17,7 @@ namespace mRemoteNG.Connection
if (string.IsNullOrEmpty(connectionInfo.Panel))
connectionInfo.Panel = Language.General;
connectionInfo.IsQuickConnect = true;
var connectionInitiator = new ConnectionInitiator();
var connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
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)
{
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();
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 mRemoteNG.App;
using System.Collections.Generic;
using System.IO;
using mRemoteNG.App.Info;
using mRemoteNG.Config;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.CredentialProviderSerializer;
using mRemoteNG.Config.Serializers.CredentialSerializer;
using mRemoteNG.Security.Factories;
using mRemoteNG.Credential.Repositories;
namespace mRemoteNG.Credential
{
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
public CredentialServiceFacade Build()
public CredentialService Build()
{
var cryptoFromSettings = new CryptoProviderFactoryFromSettings();
var credRepoSerializer = new XmlCredentialPasswordEncryptorDecorator(
cryptoFromSettings.Build(),
new XmlCredentialRecordSerializer());
var credRepoDeserializer =
new XmlCredentialPasswordDecryptorDecorator(new XmlCredentialRecordDeserializer());
var repositoryList = new CredentialRepositoryList();
var credentialRepoListPath = Path.Combine(SettingsFileInfo.SettingsPath, "credentialRepositories.xml");
var repoListDataProvider = new FileDataProvider(credentialRepoListPath);
var repoListLoader = new CredentialRepositoryListLoader(
repoListDataProvider,
new
CredentialRepositoryListDeserializer(credRepoSerializer,
credRepoDeserializer));
var repoListSaver = new CredentialRepositoryListSaver(repoListDataProvider);
var repositoryFactories = new List<ICredentialRepositoryFactory>();
var persistor = new CredentialRepositoryListPersistor(repoListDataProvider, repositoryFactories);
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.Security;
namespace mRemoteNG.Credential
{
/// <summary>
/// Represents a named set of username/domain/password information.
/// </summary>
[TypeConverter(typeof(CredentialRecordTypeConverter))]
public interface ICredentialRecord : INotifyPropertyChanged
{
/// <summary>
/// An Id which uniquely identifies this credential record.
/// </summary>
Guid Id { get; }
/// <summary>
/// A friendly name for this credential record.
/// </summary>
string Title { get; set; }
/// <summary>
/// The username portion of the credential.
/// </summary>
string Username { get; set; }
SecureString Password { get; set; }
/// <summary>
/// The domain portion of the credential.
/// </summary>
string Domain { get; set; }
/// <summary>
/// The password
/// </summary>
SecureString Password { get; set; }
}
}

View File

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

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.Tools;
using mRemoteNG.Tools.CustomCollections;
namespace mRemoteNG.Credential
@@ -12,7 +14,9 @@ namespace mRemoteNG.Credential
void RemoveProvider(ICredentialRepository credentialProvider);
bool Contains(Guid repositoryId);
Optional<ICredentialRepository> GetProvider(Guid id);
bool Contains(ICredentialRepositoryConfig repositoryConfig);
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.Linq;
using System.Security;
using mRemoteNG.Tools;
namespace mRemoteNG.Credential.Repositories
{
@@ -10,7 +11,7 @@ namespace mRemoteNG.Credential.Repositories
private readonly List<ICredentialRepository> _repositories = new List<ICredentialRepository>();
public IEnumerable<ICredentialRepository> Repositories => _repositories;
public ICredentialRepository SelectedRepository { get; set; }
public Optional<ICredentialRepository> SelectedRepository { get; set; } = Optional<ICredentialRepository>.Empty;
public CompositeRepositoryUnlocker(IEnumerable<ICredentialRepository> repositories)
{
@@ -23,7 +24,8 @@ namespace mRemoteNG.Credential.Repositories
public void Unlock(SecureString key)
{
SelectedRepository.LoadCredentials(key);
if (SelectedRepository.Any())
SelectedRepository.First().LoadCredentials(key);
}
public void SelectNextLockedRepository()
@@ -31,10 +33,10 @@ namespace mRemoteNG.Credential.Repositories
SelectedRepository = GetNextLockedRepo();
}
private ICredentialRepository GetNextLockedRepo()
private Optional<ICredentialRepository> GetNextLockedRepo()
{
var newOrder = OrderListForNextLockedRepo();
return newOrder.Any() ? newOrder.First() : null;
return new Optional<ICredentialRepository>(newOrder.FirstOrDefault());
}
private IList<ICredentialRepository> OrderListForNextLockedRepo()
@@ -67,7 +69,10 @@ namespace mRemoteNG.Credential.Repositories
private int GetNewListStartIndex()
{
var currentItemIndex = _repositories.IndexOf(SelectedRepository);
if (!SelectedRepository.Any())
return 0;
var currentItemIndex = _repositories.IndexOf(SelectedRepository.First());
return currentItemIndex + 1;
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using mRemoteNG.Tools;
using mRemoteNG.Tools.CustomCollections;
namespace mRemoteNG.Credential.Repositories
@@ -15,7 +16,9 @@ namespace mRemoteNG.Credential.Repositories
public void AddProvider(ICredentialRepository credentialProvider)
{
if (Contains(credentialProvider.Config.Id)) return;
if (Contains(credentialProvider.Config))
return;
_credentialProviders.Add(credentialProvider);
credentialProvider.CredentialsUpdated += RaiseCredentialsUpdatedEvent;
credentialProvider.RepositoryConfigUpdated += OnRepoConfigChanged;
@@ -24,16 +27,27 @@ namespace mRemoteNG.Credential.Repositories
public void RemoveProvider(ICredentialRepository credentialProvider)
{
if (!Contains(credentialProvider.Config.Id)) return;
if (!Contains(credentialProvider.Config))
return;
credentialProvider.CredentialsUpdated -= RaiseCredentialsUpdatedEvent;
credentialProvider.RepositoryConfigUpdated -= OnRepoConfigChanged;
_credentialProviders.Remove(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()

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
{
/// <summary>
/// An Id which uniquely identifies this credential repository.
/// </summary>
Guid Id { get; }
/// <summary>
/// A friendly name for this credential repository
/// </summary>
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; }
/// <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; }
/// <summary>
/// The password necessary to unlock and access the underlying repository.
/// </summary>
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.Security;
using mRemoteNG.Config;
using mRemoteNG.Tools;
using mRemoteNG.Tools.CustomCollections;
namespace mRemoteNG.Credential.Repositories
@@ -15,26 +16,36 @@ namespace mRemoteNG.Credential.Repositories
public ICredentialRepositoryConfig Config { get; }
public IList<ICredentialRecord> CredentialRecords { get; }
public string Title => Config.Title;
public bool IsLoaded { get; private set; }
public XmlCredentialRepository(ICredentialRepositoryConfig config,
CredentialRecordSaver credentialRecordSaver,
CredentialRecordLoader credentialRecordLoader)
/// <summary>
/// Creates a new <see cref="XmlCredentialRepository"/> instance,
/// 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)
throw new ArgumentNullException(nameof(config));
if (credentialRecordSaver == null)
throw new ArgumentNullException(nameof(credentialRecordSaver));
if (credentialRecordLoader == null)
throw new ArgumentNullException(nameof(credentialRecordLoader));
Config = config.ThrowIfNull(nameof(config));
_credentialRecordSaver = credentialRecordSaver.ThrowIfNull(nameof(credentialRecordSaver));
_credentialRecordLoader = credentialRecordLoader.ThrowIfNull(nameof(credentialRecordLoader));
IsLoaded = isLoaded;
Config = config;
CredentialRecords = new FullyObservableCollection<ICredentialRecord>();
((FullyObservableCollection<ICredentialRecord>)CredentialRecords).CollectionUpdated +=
RaiseCredentialsUpdatedEvent;
((FullyObservableCollection<ICredentialRecord>) CredentialRecords).CollectionUpdated += RaiseCredentialsUpdatedEvent;
Config.PropertyChanged += (sender, args) => RaiseRepositoryConfigUpdatedEvent(args);
_credentialRecordSaver = credentialRecordSaver;
_credentialRecordLoader = credentialRecordLoader;
}
public void LoadCredentials(SecureString key)
@@ -42,7 +53,9 @@ namespace mRemoteNG.Credential.Repositories
var credentials = _credentialRecordLoader.Load(key);
foreach (var newCredential in credentials)
{
if (ThisIsADuplicateCredentialRecord(newCredential)) continue;
if (ThisIsADuplicateCredentialRecord(newCredential))
continue;
CredentialRecords.Add(newCredential);
}
@@ -63,7 +76,9 @@ namespace mRemoteNG.Credential.Repositories
public void SaveCredentials(SecureString key)
{
if (!IsLoaded) return;
if (!IsLoaded)
return;
_credentialRecordSaver.Save(CredentialRecords, key);
}

View File

@@ -1,55 +1,41 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Collections.Generic;
using mRemoteNG.Config;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Tools;
namespace mRemoteNG.Credential.Repositories
{
public class XmlCredentialRepositoryFactory
public class XmlCredentialRepositoryFactory : ICredentialRepositoryFactory
{
private readonly ISecureSerializer<IEnumerable<ICredentialRecord>, string> _serializer;
private readonly ISecureDeserializer<string, IEnumerable<ICredentialRecord>> _deserializer;
public XmlCredentialRepositoryFactory(ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer,
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer)
public XmlCredentialRepositoryFactory(
ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer,
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer)
{
if (serializer == null)
throw new ArgumentNullException(nameof(serializer));
if (deserializer == null)
throw new ArgumentNullException(nameof(deserializer));
_serializer = serializer;
_deserializer = deserializer;
_serializer = serializer.ThrowIfNull(nameof(serializer));
_deserializer = deserializer.ThrowIfNull(nameof(deserializer));
}
public ICredentialRepository Build(ICredentialRepositoryConfig config)
{
return BuildXmlRepo(config);
}
public string SupportsConfigType { get; } = "Xml";
public ICredentialRepository Build(XElement repositoryXElement)
{
var stringId = repositoryXElement.Attribute("Id")?.Value;
Guid id;
Guid.TryParse(stringId, out 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 BuildXmlRepo(config);
}
private ICredentialRepository BuildXmlRepo(ICredentialRepositoryConfig config)
/// <summary>
/// Creates a new <see cref="XmlCredentialRepository"/> instance for
/// the given <see cref="ICredentialRepositoryConfig"/>.
/// </summary>
/// <param name="config"></param>
/// <param name="isLoaded">
/// Does this instance represent a repository that is already loaded?
/// </param>
public ICredentialRepository Build(ICredentialRepositoryConfig config, bool isLoaded = false)
{
var dataProvider = new FileDataProvider(config.Source);
var saver = new CredentialRecordSaver(dataProvider, _serializer);
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>
/// Sucht eine lokalisierte Zeichenfolge, die Default Inheritance ähnelt.
/// </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>
/// Sucht eine lokalisierte Zeichenfolge, die Copy ähnelt.
/// </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>
/// Sucht eine lokalisierte Zeichenfolge, die Put your notes or a description for the host here. ähnelt.
/// </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>
/// Sucht eine lokalisierte Zeichenfolge, die Report a Bug ähnelt.
/// </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>
/// Sucht eine lokalisierte Zeichenfolge, die Reset layout ähnelt.
/// </summary>

View File

@@ -2153,4 +2153,19 @@ Nightly Channel includes Alphas, Betas &amp; Release Candidates.</value>
<data name="SmartCard" xml:space="preserve">
<value>SmartCard</value>
</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>

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>
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
/// </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>
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
/// </summary>

View File

@@ -337,4 +337,10 @@
<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>
</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>

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.Diagnostics.DebuggerNonUserCodeAttribute()]
[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.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
@@ -2797,6 +2773,18 @@ namespace mRemoteNG.Properties {
[global::System.Configuration.UserScopedSettingAttribute()]
[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")]
public bool AlwaysShowConnectionTabs {
get {

View File

@@ -59,15 +59,6 @@
<Setting Name="EmptyCredentials" Type="System.String" Scope="User">
<Value Profile="(Default)">noinfo</Value>
</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">
<Value Profile="(Default)">False</Value>
</Setting>
@@ -689,11 +680,17 @@
<Setting Name="StartUpPanelName" Type="System.String" Scope="User">
<Value Profile="(Default)">General</Value>
</Setting>
<Setting Name="CloseCredentialUnlockerDialogAfterLastUnlock" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="TrackActiveConnectionInConnectionTree" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="PlaceSearchBarAboveConnectionTree" Type="System.Boolean" Scope="User">
<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 Name="AlwaysShowConnectionTabs" Type="System.Boolean" Scope="User">
<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="Icon" 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="Password" type="xs:string" use="required" />
<xs:attribute name="CredentialId" 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="RdpVersion" type="xs:string" use="required" />
@@ -115,9 +115,9 @@
<xs:attribute name="InheritDisableCursorShadow" 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="InheritCredentialId" 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="InheritPassword" 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="InheritRdpVersion" type="xs:boolean" use="optional" />
@@ -137,7 +137,6 @@
<xs:attribute name="InheritAutomaticResize" 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="InheritUsername" 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="InheritRDPMinutesToIdleTimeout" type="xs:boolean" use="optional" />

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using mRemoteNG.Connection;
using mRemoteNG.Container;
namespace mRemoteNG.Tools
{
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);
}
@@ -69,5 +71,19 @@ namespace mRemoteNG.Tools
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
{
private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator();
private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
private string _displayName;
private string _fileName;
private bool _waitForExit;
@@ -152,7 +152,7 @@ namespace mRemoteNG.Tools
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.FileName = argParser.ParseArguments(FileName);
process.StartInfo.Arguments = argParser.ParseArguments(Arguments);

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using mRemoteNG.App;
using System.Linq;
using System.Text.RegularExpressions;
using mRemoteNG.Connection;
using mRemoteNG.Properties;
using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Credential;
using mRemoteNG.Security;
using mRemoteNG.Tools.Cmdline;
namespace mRemoteNG.Tools
@@ -11,10 +12,12 @@ namespace mRemoteNG.Tools
public class ExternalToolArgumentParser
{
private readonly ConnectionInfo _connectionInfo;
private readonly ICredentialService _credentialService;
public ExternalToolArgumentParser(ConnectionInfo connectionInfo)
public ExternalToolArgumentParser(ConnectionInfo connectionInfo, ICredentialService credentialService)
{
_connectionInfo = connectionInfo;
_credentialService = credentialService.ThrowIfNull(nameof(credentialService));
}
public string ParseArguments(string input)
@@ -162,56 +165,55 @@ namespace mRemoteNG.Tools
private string GetVariableReplacement(string variable, string original)
{
var replacement = "";
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;
}
var normalizedVariable = variable.ToLowerInvariant();
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)

View File

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

View File

@@ -15,7 +15,7 @@ namespace mRemoteNG.Tools
private readonly NotifyIcon _nI;
private readonly ContextMenuStrip _cMen;
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;
public bool Disposed { get; private set; }

View File

@@ -43,7 +43,8 @@ namespace mRemoteNG.Tools
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
? new Optional<TOut>(value.Value)

View File

@@ -9,7 +9,7 @@ using mRemoteNG.Tree.Root;
namespace mRemoteNG.Tree
{
public sealed class ConnectionTreeModel : INotifyCollectionChanged, INotifyPropertyChanged
public sealed class ConnectionTreeModel : IConnectionTreeModel
{
public List<ContainerInfo> RootNodes { get; } = new List<ContainerInfo>();
@@ -48,7 +48,7 @@ namespace mRemoteNG.Tree
public IEnumerable<ConnectionInfo> GetRecursiveChildList(ContainerInfo container)
{
return container.GetRecursiveChildList();
return container?.GetRecursiveChildList() ?? new ConnectionInfo[0];
}
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
{
private readonly ConnectionTreeModel _connectionTreeModel;
private readonly IConnectionTreeModel _connectionTreeModel;
private List<ConnectionInfo> Matches { get; set; }
public ConnectionInfo CurrentMatch { get; private set; }
public NodeSearcher(ConnectionTreeModel connectionTreeModel)
public NodeSearcher(IConnectionTreeModel connectionTreeModel)
{
_connectionTreeModel = connectionTreeModel;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,17 @@ namespace mRemoteNG.UI.Controls
{
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()
{

View File

@@ -8,7 +8,7 @@ namespace mRemoteNG.UI.Controls
public partial class CredentialRecordListBox : ListBox
{
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 CredentialRecordListBox(IEnumerable<ICredentialRecord> listOfCredentialRecords)

View File

@@ -46,6 +46,7 @@
this.objectListView1.AllColumns.Add(this.olvColumnCredentialId);
this.objectListView1.AllColumns.Add(this.olvColumnRepositoryTitle);
this.objectListView1.AllColumns.Add(this.olvColumnRepositorySource);
this.objectListView1.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.objectListView1.CellEditUseWholeCell = false;
this.objectListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.olvColumnTitle,
@@ -108,7 +109,6 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
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.Size = new System.Drawing.Size(367, 204);
((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()
{
InitializeComponent();
ApplyLanguage();
var display = new DisplayProperties();
secureTextBox1.Width = display.ScaleWidth(Width);
secureTextBox2.Width = display.ScaleWidth(Width);
secureTextBox1.TextChanged += OnSecureTextBoxTextChanged;
secureTextBox2.TextChanged += OnSecureTextBoxTextChanged;
}
@@ -55,6 +59,14 @@ namespace mRemoteNG.UI.Controls
secureTextBox2.Text = text;
}
private void ApplyLanguage()
{
//TODO: RESX
//labelFirstPasswordBox.Text = Language.strNewPassword;
//labelSecondPasswordBox.Text = Language.strVerifyPassword;
//labelPasswordsDontMatch.Text = Language.strPasswordsDontMatch;
}
private bool Verify()
{
return secureTextBox1.SecString.Length == secureTextBox2.SecString.Length &&
@@ -90,16 +102,28 @@ namespace mRemoteNG.UI.Controls
{
PasswordsMatch = false;
SecureString = null;
RaiseNotVerifiedEvent();
}
TogglePasswordMatchIndicator(PasswordsMatch);
}
/// <summary>
/// Raised when the two given passwords go from not matching to matching (verified).
/// </summary>
public event EventHandler Verified;
private void RaiseVerifiedEvent()
{
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