diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT
index ebc2917c..1090b83d 100644
--- a/CHANGELOG.TXT
+++ b/CHANGELOG.TXT
@@ -11,6 +11,21 @@ Features/Enhancements:
#928: Add context menu items to 'Close all but this' and 'Close all tabs to the right'
+1.76.12 (2018-11-08):
+
+Features/Enhancements:
+----------------------
+#1180: Allow saving certain connection properties locally when using database
+
+Fixes:
+------
+#1181: Connections sometimes dont immediately load when switching to sql feature
+#1173: Fixed memory leak when loading connections multiple times
+#1168: Autohide Connection and Config tab won't open when ssh connection active
+#1134: Fixed issue where opening a connection opens same connection on other clients when using database feature
+#449: Encrypt passwords saved to database
+
+
1.76.11 (2018-10-18):
Fixes:
diff --git a/mRemoteNGTests/Config/Serializers/DataTableDeserializerTests.cs b/mRemoteNGTests/Config/Serializers/DataTableDeserializerTests.cs
index edba5616..8d7cc77f 100644
--- a/mRemoteNGTests/Config/Serializers/DataTableDeserializerTests.cs
+++ b/mRemoteNGTests/Config/Serializers/DataTableDeserializerTests.cs
@@ -1,10 +1,12 @@
using System.Data;
-using System.Linq;
-using mRemoteNG.Config.Serializers;
+using System.Security;
+using mRemoteNG.Config.Serializers.MsSql;
using mRemoteNG.Connection;
using mRemoteNG.Security;
+using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Tree;
using mRemoteNGTests.TestHelpers;
+using NSubstitute;
using NUnit.Framework;
namespace mRemoteNGTests.Config.Serializers
@@ -12,30 +14,37 @@ namespace mRemoteNGTests.Config.Serializers
public class DataTableDeserializerTests
{
private DataTableDeserializer _deserializer;
+ private ICryptographyProvider _cryptographyProvider;
+
+ [SetUp]
+ public void Setup()
+ {
+ _cryptographyProvider = new LegacyRijndaelCryptographyProvider();
+ }
[Test]
public void WeCanDeserializeATree()
{
var model = CreateConnectionTreeModel();
var dataTable = CreateDataTable(model.RootNodes[0]);
- _deserializer = new DataTableDeserializer();
+ _deserializer = new DataTableDeserializer(_cryptographyProvider, new SecureString());
var output = _deserializer.Deserialize(dataTable);
- Assert.That(output.GetRecursiveChildList().Count(), Is.EqualTo(model.GetRecursiveChildList().Count()));
+ Assert.That(output.GetRecursiveChildList().Count, Is.EqualTo(model.GetRecursiveChildList().Count));
}
[Test]
public void WeCanDeserializeASingleEntry()
{
var dataTable = CreateDataTable(new ConnectionInfo());
- _deserializer = new DataTableDeserializer();
+ _deserializer = new DataTableDeserializer(_cryptographyProvider, new SecureString());
var output = _deserializer.Deserialize(dataTable);
- Assert.That(output.GetRecursiveChildList().Count(), Is.EqualTo(1));
+ Assert.That(output.GetRecursiveChildList().Count, Is.EqualTo(1));
}
private DataTable CreateDataTable(ConnectionInfo tableContent)
{
- var serializer = new DataTableSerializer(new SaveFilter());
+ var serializer = new DataTableSerializer(new SaveFilter(), _cryptographyProvider, new SecureString());
return serializer.Serialize(tableContent);
}
diff --git a/mRemoteNGTests/Config/Serializers/DataTableSerializerTests.cs b/mRemoteNGTests/Config/Serializers/DataTableSerializerTests.cs
index 5176fa1a..6292419a 100644
--- a/mRemoteNGTests/Config/Serializers/DataTableSerializerTests.cs
+++ b/mRemoteNGTests/Config/Serializers/DataTableSerializerTests.cs
@@ -1,11 +1,15 @@
using System.Linq;
+using System.Security;
using mRemoteNG.Config.Serializers;
+using mRemoteNG.Config.Serializers.MsSql;
using mRemoteNG.Connection;
using mRemoteNG.Container;
using mRemoteNG.Security;
+using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
using mRemoteNGTests.TestHelpers;
+using NSubstitute;
using NUnit.Framework;
namespace mRemoteNGTests.Config.Serializers
@@ -19,7 +23,10 @@ namespace mRemoteNGTests.Config.Serializers
public void Setup()
{
_saveFilter = new SaveFilter();
- _dataTableSerializer = new DataTableSerializer(_saveFilter);
+ _dataTableSerializer = new DataTableSerializer(
+ _saveFilter,
+ new LegacyRijndaelCryptographyProvider(),
+ new SecureString());
}
[Test]
diff --git a/mRemoteV1/App/Info/SettingsFileInfo.cs b/mRemoteV1/App/Info/SettingsFileInfo.cs
index 812259a7..a91d9497 100644
--- a/mRemoteV1/App/Info/SettingsFileInfo.cs
+++ b/mRemoteV1/App/Info/SettingsFileInfo.cs
@@ -2,14 +2,15 @@
using System.IO;
using System.Reflection;
using System.Windows.Forms;
+using mRemoteNG.Connection;
namespace mRemoteNG.App.Info
{
public static class SettingsFileInfo
{
- private static readonly string ExePath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
+ private static readonly string ExePath = Path.GetDirectoryName(Assembly.GetAssembly(typeof(ConnectionInfo))?.Location);
- public static string SettingsPath { get; } = Runtime.IsPortableEdition ? ExePath : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + Application.ProductName;
+ public static string SettingsPath => Runtime.IsPortableEdition ? ExePath : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + Application.ProductName;
public static string LayoutFileName { get; } = "pnlLayout.xml";
public static string ExtAppsFilesName { get; } = "extApps.xml";
public static string ThemesFileName { get; } = "Themes.xml";
diff --git a/mRemoteV1/App/Runtime.cs b/mRemoteV1/App/Runtime.cs
index b1921a8c..f637a001 100644
--- a/mRemoteV1/App/Runtime.cs
+++ b/mRemoteV1/App/Runtime.cs
@@ -1,8 +1,3 @@
-using System;
-using System.IO;
-using System.Security;
-using System.Threading;
-using System.Windows.Forms;
using mRemoteNG.App.Info;
using mRemoteNG.Config.Putty;
using mRemoteNG.Connection;
@@ -15,6 +10,11 @@ using mRemoteNG.Tree.Root;
using mRemoteNG.UI;
using mRemoteNG.UI.Forms;
using mRemoteNG.UI.TaskDialog;
+using System;
+using System.IO;
+using System.Security;
+using System.Threading;
+using System.Windows.Forms;
namespace mRemoteNG.App
{
@@ -43,19 +43,23 @@ namespace mRemoteNG.App
#region Connections Loading/Saving
public static void LoadConnectionsAsync()
{
- _withDialog = false;
-
var t = new Thread(LoadConnectionsBGd);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
- private static bool _withDialog;
private static void LoadConnectionsBGd()
{
- LoadConnections(_withDialog);
+ LoadConnections();
}
+ ///
+ ///
+ ///
+ ///
+ /// Should we show the file selection dialog to allow the user to select
+ /// a connection file
+ ///
public static void LoadConnections(bool withDialog = false)
{
var connectionFileName = "";
@@ -65,18 +69,19 @@ namespace mRemoteNG.App
// disable sql update checking while we are loading updates
ConnectionsService.RemoteConnectionsSyncronizer?.Disable();
- if (!Settings.Default.UseSQLServer)
+ if (withDialog)
{
- if (withDialog)
- {
- var loadDialog = DialogFactory.BuildLoadConnectionsDialog();
- if (loadDialog.ShowDialog() != DialogResult.OK) return;
- connectionFileName = loadDialog.FileName;
- }
- else
- {
- connectionFileName = ConnectionsService.GetStartupConnectionFileName();
- }
+ var loadDialog = DialogFactory.BuildLoadConnectionsDialog();
+ if (loadDialog.ShowDialog() != DialogResult.OK)
+ return;
+
+ connectionFileName = loadDialog.FileName;
+ Settings.Default.UseSQLServer = false;
+ Settings.Default.Save();
+ }
+ else if (!Settings.Default.UseSQLServer)
+ {
+ connectionFileName = ConnectionsService.GetStartupConnectionFileName();
}
ConnectionsService.LoadConnections(Settings.Default.UseSQLServer, false, connectionFileName);
diff --git a/mRemoteV1/Config/Connections/CsvConnectionsSaver.cs b/mRemoteV1/Config/Connections/CsvConnectionsSaver.cs
index 28e7b79e..598cea19 100644
--- a/mRemoteV1/Config/Connections/CsvConnectionsSaver.cs
+++ b/mRemoteV1/Config/Connections/CsvConnectionsSaver.cs
@@ -24,7 +24,7 @@ namespace mRemoteNG.Config.Connections
_saveFilter = saveFilter;
}
- public void Save(ConnectionTreeModel connectionTreeModel)
+ public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
var csvConnectionsSerializer = new CsvConnectionsSerializerMremotengFormat(_saveFilter, Runtime.CredentialProviderCatalog);
var dataProvider = new FileDataProvider(_connectionFileName);
diff --git a/mRemoteV1/Config/Connections/IConnectionsLoader.cs b/mRemoteV1/Config/Connections/IConnectionsLoader.cs
new file mode 100644
index 00000000..a4669757
--- /dev/null
+++ b/mRemoteV1/Config/Connections/IConnectionsLoader.cs
@@ -0,0 +1,9 @@
+using mRemoteNG.Tree;
+
+namespace mRemoteNG.Config.Connections
+{
+ public interface IConnectionsLoader
+ {
+ ConnectionTreeModel Load();
+ }
+}
\ No newline at end of file
diff --git a/mRemoteV1/Config/Connections/Multiuser/RemoteConnectionsSyncronizer.cs b/mRemoteV1/Config/Connections/Multiuser/RemoteConnectionsSyncronizer.cs
index a52ffea3..1a4c2075 100644
--- a/mRemoteV1/Config/Connections/Multiuser/RemoteConnectionsSyncronizer.cs
+++ b/mRemoteV1/Config/Connections/Multiuser/RemoteConnectionsSyncronizer.cs
@@ -1,6 +1,7 @@
-using System;
+using mRemoteNG.App;
+using System;
using System.Timers;
-using mRemoteNG.App;
+
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.Config.Connections.Multiuser
diff --git a/mRemoteV1/Config/Connections/Multiuser/SqlConnectionsUpdateChecker.cs b/mRemoteV1/Config/Connections/Multiuser/SqlConnectionsUpdateChecker.cs
index fdd8dc1b..af3ab56f 100644
--- a/mRemoteV1/Config/Connections/Multiuser/SqlConnectionsUpdateChecker.cs
+++ b/mRemoteV1/Config/Connections/Multiuser/SqlConnectionsUpdateChecker.cs
@@ -1,11 +1,11 @@
using mRemoteNG.App;
+using mRemoteNG.Config.Connections.Multiuser;
+using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Messages;
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
-using mRemoteNG.Config.Connections.Multiuser;
-using mRemoteNG.Config.DatabaseConnectors;
namespace mRemoteNG.Config.Connections
{
@@ -13,7 +13,7 @@ namespace mRemoteNG.Config.Connections
{
private readonly SqlDatabaseConnector _sqlConnector;
private readonly SqlCommand _sqlQuery;
- private DateTime _lastUpdateTime;
+ private DateTime LastUpdateTime => Runtime.ConnectionsService.LastSqlUpdate;
private DateTime _lastDatabaseUpdateTime;
@@ -21,7 +21,6 @@ namespace mRemoteNG.Config.Connections
{
_sqlConnector = DatabaseConnectorFactory.SqlDatabaseConnectorFromSettings();
_sqlQuery = new SqlCommand("SELECT * FROM tblUpdate", _sqlConnector.SqlConnection);
- _lastUpdateTime = default(DateTime);
_lastDatabaseUpdateTime = default(DateTime);
}
@@ -58,14 +57,14 @@ namespace mRemoteNG.Config.Connections
private bool DatabaseIsMoreUpToDateThanUs()
{
var lastUpdateInDb = GetLastUpdateTimeFromDbResponse();
- var IAmTheLastoneUpdated = CheckIfIAmTheLastOneUpdated(lastUpdateInDb);
- return (lastUpdateInDb > _lastUpdateTime && !IAmTheLastoneUpdated);
+ var amTheLastoneUpdated = CheckIfIAmTheLastOneUpdated(lastUpdateInDb);
+ return (lastUpdateInDb > LastUpdateTime && !amTheLastoneUpdated);
}
private bool CheckIfIAmTheLastOneUpdated(DateTime lastUpdateInDb)
{
- DateTime LastSqlUpdateWithoutMilliseconds = new DateTime(Runtime.ConnectionsService.LastSqlUpdate.Ticks - (Runtime.ConnectionsService.LastSqlUpdate.Ticks % TimeSpan.TicksPerSecond), Runtime.ConnectionsService.LastSqlUpdate.Kind);
- return lastUpdateInDb == LastSqlUpdateWithoutMilliseconds;
+ DateTime lastSqlUpdateWithoutMilliseconds = new DateTime(LastUpdateTime.Ticks - (LastUpdateTime.Ticks % TimeSpan.TicksPerSecond), LastUpdateTime.Kind);
+ return lastUpdateInDb == lastSqlUpdateWithoutMilliseconds;
}
private DateTime GetLastUpdateTimeFromDbResponse()
@@ -104,10 +103,9 @@ namespace mRemoteNG.Config.Connections
public event ConnectionsUpdateAvailableEventHandler ConnectionsUpdateAvailable;
private void RaiseConnectionsUpdateAvailableEvent()
{
+ Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Remote connection update is available");
var args = new ConnectionsUpdateAvailableEventArgs(_sqlConnector, _lastDatabaseUpdateTime);
ConnectionsUpdateAvailable?.Invoke(this, args);
- if(args.Handled)
- _lastUpdateTime = _lastDatabaseUpdateTime;
}
public void Dispose()
diff --git a/mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs b/mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs
index 038b7fff..d36648af 100644
--- a/mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs
+++ b/mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs
@@ -33,7 +33,7 @@ namespace mRemoteNG.Config.Connections
private void ConnectionTreeModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
- SaveConnectionOnEdit();
+ SaveConnectionOnEdit(propertyChangedEventArgs.PropertyName);
}
private void ConnectionTreeModelOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
@@ -41,14 +41,14 @@ namespace mRemoteNG.Config.Connections
SaveConnectionOnEdit();
}
- private void SaveConnectionOnEdit()
+ private void SaveConnectionOnEdit(string propertyName = "")
{
if (!mRemoteNG.Settings.Default.SaveConnectionsAfterEveryEdit)
return;
if (FrmMain.Default.IsClosing)
return;
- _connectionsService.SaveConnectionsAsync();
+ _connectionsService.SaveConnectionsAsync(propertyName);
}
}
}
diff --git a/mRemoteV1/Config/Connections/SqlConnectionsLoader.cs b/mRemoteV1/Config/Connections/SqlConnectionsLoader.cs
index eb2107d8..ed566424 100644
--- a/mRemoteV1/Config/Connections/SqlConnectionsLoader.cs
+++ b/mRemoteV1/Config/Connections/SqlConnectionsLoader.cs
@@ -1,22 +1,89 @@
-using mRemoteNG.Config.DatabaseConnectors;
+using System;
+using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
+using mRemoteNG.Config.Serializers.MsSql;
using mRemoteNG.Config.Serializers.Versioning;
+using mRemoteNG.Container;
+using mRemoteNG.Tools;
using mRemoteNG.Tree;
+using mRemoteNG.Tree.Root;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security;
+using mRemoteNG.Security;
+using mRemoteNG.Security.Authentication;
+using mRemoteNG.Security.SymmetricEncryption;
namespace mRemoteNG.Config.Connections
{
- public class SqlConnectionsLoader
+ public class SqlConnectionsLoader : IConnectionsLoader
{
+ private readonly IDeserializer> _localConnectionPropertiesDeserializer;
+ private readonly IDataProvider _dataProvider;
+
+ public Func> AuthenticationRequestor { get; set; } =
+ () => MiscTools.PasswordDialog("", false);
+
+ public SqlConnectionsLoader(
+ IDeserializer> localConnectionPropertiesDeserializer,
+ IDataProvider dataProvider)
+ {
+ _localConnectionPropertiesDeserializer = localConnectionPropertiesDeserializer.ThrowIfNull(nameof(localConnectionPropertiesDeserializer));
+ _dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
+ }
+
public ConnectionTreeModel Load()
{
var connector = DatabaseConnectorFactory.SqlDatabaseConnectorFromSettings();
var dataProvider = new SqlDataProvider(connector);
+ var metaDataRetriever = new SqlDatabaseMetaDataRetriever();
var databaseVersionVerifier = new SqlDatabaseVersionVerifier(connector);
- databaseVersionVerifier.VerifyDatabaseVersion();
+ var cryptoProvider = new LegacyRijndaelCryptographyProvider();
+
+ var metaData = metaDataRetriever.GetDatabaseMetaData(connector);
+ var decryptionKey = GetDecryptionKey(metaData);
+
+ if (!decryptionKey.Any())
+ throw new Exception("Could not load SQL connections");
+
+ databaseVersionVerifier.VerifyDatabaseVersion(metaData.ConfVersion);
var dataTable = dataProvider.Load();
- var deserializer = new DataTableDeserializer();
- return deserializer.Deserialize(dataTable);
+ var deserializer = new DataTableDeserializer(cryptoProvider, decryptionKey.First());
+ var connectionTree = deserializer.Deserialize(dataTable);
+ ApplyLocalConnectionProperties(connectionTree.RootNodes.First(i => i is RootNodeInfo));
+ return connectionTree;
+ }
+
+ private Optional GetDecryptionKey(SqlConnectionListMetaData metaData)
+ {
+ var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
+ var cipherText = metaData.Protected;
+ var authenticator = new PasswordAuthenticator(cryptographyProvider, cipherText, AuthenticationRequestor);
+ var authenticated = authenticator.Authenticate(new RootNodeInfo(RootNodeType.Connection).DefaultPassword.ConvertToSecureString());
+
+ if (authenticated)
+ return authenticator.LastAuthenticatedPassword;
+ return Optional.Empty;
+ }
+
+ private void ApplyLocalConnectionProperties(ContainerInfo rootNode)
+ {
+ var localPropertiesXml = _dataProvider.Load();
+ var localConnectionProperties = _localConnectionPropertiesDeserializer.Deserialize(localPropertiesXml);
+
+ rootNode
+ .GetRecursiveChildList()
+ .Join(localConnectionProperties,
+ con => con.ConstantID,
+ locals => locals.ConnectionId,
+ (con, locals) => new {Connection = con, LocalProperties = locals})
+ .ForEach(x =>
+ {
+ x.Connection.PleaseConnect = x.LocalProperties.Connected;
+ if (x.Connection is ContainerInfo container)
+ container.IsExpanded = x.LocalProperties.Expanded;
+ });
}
}
}
\ No newline at end of file
diff --git a/mRemoteV1/Config/Connections/SqlConnectionsSaver.cs b/mRemoteV1/Config/Connections/SqlConnectionsSaver.cs
index 93a5868b..922bee10 100644
--- a/mRemoteV1/Config/Connections/SqlConnectionsSaver.cs
+++ b/mRemoteV1/Config/Connections/SqlConnectionsSaver.cs
@@ -1,13 +1,9 @@
-using System;
-using System.Data.SqlClient;
-using System.Globalization;
-using System.Linq;
-using System.Security;
-using mRemoteNG.App;
+using mRemoteNG.App;
using mRemoteNG.App.Info;
using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
+using mRemoteNG.Config.Serializers.MsSql;
using mRemoteNG.Config.Serializers.Versioning;
using mRemoteNG.Container;
using mRemoteNG.Messages;
@@ -16,6 +12,13 @@ using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Tools;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Globalization;
+using System.Linq;
+using System.Security;
+using mRemoteNG.Connection;
namespace mRemoteNG.Config.Connections
{
@@ -23,40 +26,87 @@ namespace mRemoteNG.Config.Connections
{
private SecureString _password = Runtime.EncryptionKey;
private readonly SaveFilter _saveFilter;
+ private readonly ISerializer, string> _localPropertiesSerializer;
+ private readonly IDataProvider _dataProvider;
- public SqlConnectionsSaver(SaveFilter saveFilter)
+ public SqlConnectionsSaver(
+ SaveFilter saveFilter,
+ ISerializer, string> localPropertieSerializer,
+ IDataProvider localPropertiesDataProvider)
{
if (saveFilter == null)
throw new ArgumentNullException(nameof(saveFilter));
_saveFilter = saveFilter;
+ _localPropertiesSerializer = localPropertieSerializer.ThrowIfNull(nameof(localPropertieSerializer));
+ _dataProvider = localPropertiesDataProvider.ThrowIfNull(nameof(localPropertiesDataProvider));
}
- public void Save(ConnectionTreeModel connectionTreeModel)
+ public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
+ var rootTreeNode = connectionTreeModel.RootNodes.OfType().First();
+
+ UpdateLocalConnectionProperties(rootTreeNode);
+
+ if (PropertyIsLocalOnly(propertyNameTrigger))
+ {
+ Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg,
+ $"Property {propertyNameTrigger} is local only. Not saving to database.");
+ return;
+ }
+
if (SqlUserIsReadOnly())
{
Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, "Trying to save connection tree but the SQL read only checkbox is checked, aborting!");
return;
}
-
using (var sqlConnector = DatabaseConnectorFactory.SqlDatabaseConnectorFromSettings())
{
sqlConnector.Connect();
var databaseVersionVerifier = new SqlDatabaseVersionVerifier(sqlConnector);
+ var metaDataRetriever = new SqlDatabaseMetaDataRetriever();
+ var metaData = metaDataRetriever.GetDatabaseMetaData(sqlConnector);
- if (!databaseVersionVerifier.VerifyDatabaseVersion())
+ if (!databaseVersionVerifier.VerifyDatabaseVersion(metaData.ConfVersion))
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.strErrorConnectionListSaveFailed);
return;
}
- var rootTreeNode = connectionTreeModel.RootNodes.OfType().First();
-
UpdateRootNodeTable(rootTreeNode, sqlConnector);
UpdateConnectionsTable(rootTreeNode, sqlConnector);
UpdateUpdatesTable(sqlConnector);
}
+
+ Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Saved connections to database");
+ }
+
+ ///
+ /// Determines if a given property name should be only saved
+ /// locally.
+ ///
+ ///
+ /// The name of the property that triggered the save event
+ ///
+ ///
+ private bool PropertyIsLocalOnly(string property)
+ {
+ return property == nameof(ConnectionInfo.OpenConnections) ||
+ property == nameof(ContainerInfo.IsExpanded);
+ }
+
+ private void UpdateLocalConnectionProperties(ContainerInfo rootNode)
+ {
+ var a = rootNode.GetRecursiveChildList().Select(info => new LocalConnectionPropertiesModel
+ {
+ ConnectionId = info.ConstantID,
+ Connected = info.OpenConnections.Count > 0,
+ Expanded = info is ContainerInfo c && c.IsExpanded
+ });
+
+ var serializedProperties = _localPropertiesSerializer.Serialize(a);
+ _dataProvider.Save(serializedProperties);
+ Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Saved local connection properties");
}
private void UpdateRootNodeTable(RootNodeInfo rootTreeNode, SqlDatabaseConnector sqlDatabaseConnector)
@@ -99,13 +149,15 @@ namespace mRemoteNG.Config.Connections
}
}
- private void UpdateConnectionsTable(ContainerInfo rootTreeNode, SqlDatabaseConnector sqlDatabaseConnector)
+ private void UpdateConnectionsTable(RootNodeInfo rootTreeNode, SqlDatabaseConnector sqlDatabaseConnector)
{
- var sqlQuery = new SqlCommand("DELETE FROM tblCons", sqlDatabaseConnector.SqlConnection);
- sqlQuery.ExecuteNonQuery();
- var serializer = new DataTableSerializer(_saveFilter);
+ var cryptoProvider = new LegacyRijndaelCryptographyProvider();
+ var serializer = new DataTableSerializer(_saveFilter, cryptoProvider, rootTreeNode.PasswordString.ConvertToSecureString());
var dataTable = serializer.Serialize(rootTreeNode);
var dataProvider = new SqlDataProvider(sqlDatabaseConnector);
+
+ var sqlQuery = new SqlCommand("DELETE FROM tblCons", sqlDatabaseConnector.SqlConnection);
+ sqlQuery.ExecuteNonQuery();
dataProvider.Save(dataTable);
}
diff --git a/mRemoteV1/Config/Connections/XmlConnectionsLoader.cs b/mRemoteV1/Config/Connections/XmlConnectionsLoader.cs
index c79e13d8..71a02ebe 100644
--- a/mRemoteV1/Config/Connections/XmlConnectionsLoader.cs
+++ b/mRemoteV1/Config/Connections/XmlConnectionsLoader.cs
@@ -1,14 +1,14 @@
-using System;
-using System.IO;
-using System.Security;
-using mRemoteNG.Config.DataProviders;
+using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.Xml;
using mRemoteNG.Tools;
using mRemoteNG.Tree;
+using System;
+using System.IO;
+using System.Security;
namespace mRemoteNG.Config.Connections
{
- public class XmlConnectionsLoader
+ public class XmlConnectionsLoader : IConnectionsLoader
{
private readonly string _connectionFilePath;
diff --git a/mRemoteV1/Config/Connections/XmlConnectionsSaver.cs b/mRemoteV1/Config/Connections/XmlConnectionsSaver.cs
index 060c2c2b..6dad366d 100644
--- a/mRemoteV1/Config/Connections/XmlConnectionsSaver.cs
+++ b/mRemoteV1/Config/Connections/XmlConnectionsSaver.cs
@@ -27,7 +27,7 @@ namespace mRemoteNG.Config.Connections
_saveFilter = saveFilter;
}
- public void Save(ConnectionTreeModel connectionTreeModel)
+ public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
try
{
diff --git a/mRemoteV1/Config/CredentialRepositoryListSaver.cs b/mRemoteV1/Config/CredentialRepositoryListSaver.cs
index c5000024..6200eb72 100644
--- a/mRemoteV1/Config/CredentialRepositoryListSaver.cs
+++ b/mRemoteV1/Config/CredentialRepositoryListSaver.cs
@@ -18,7 +18,7 @@ namespace mRemoteNG.Config
_dataProvider = dataProvider;
}
- public void Save(IEnumerable repositories)
+ public void Save(IEnumerable repositories, string propertyNameTrigger = "")
{
var serializer = new CredentialRepositoryListSerializer();
var data = serializer.Serialize(repositories);
diff --git a/mRemoteV1/Config/ISaver.cs b/mRemoteV1/Config/ISaver.cs
index cf4caea8..1f9700be 100644
--- a/mRemoteV1/Config/ISaver.cs
+++ b/mRemoteV1/Config/ISaver.cs
@@ -2,6 +2,6 @@
{
public interface ISaver
{
- void Save(T model);
+ void Save(T model, string propertyNameTrigger = "");
}
}
\ No newline at end of file
diff --git a/mRemoteV1/Config/Serializers/DataTableDeserializer.cs b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableDeserializer.cs
similarity index 89%
rename from mRemoteV1/Config/Serializers/DataTableDeserializer.cs
rename to mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableDeserializer.cs
index 52f35877..ad01d1a2 100644
--- a/mRemoteV1/Config/Serializers/DataTableDeserializer.cs
+++ b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableDeserializer.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Linq;
-using mRemoteNG.App;
+using mRemoteNG.App;
using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.Http;
@@ -12,11 +8,28 @@ using mRemoteNG.Connection.Protocol.VNC;
using mRemoteNG.Container;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Security;
+using mRemoteNG.Security;
+using mRemoteNG.Security.SymmetricEncryption;
+using mRemoteNG.Tools;
-namespace mRemoteNG.Config.Serializers
+namespace mRemoteNG.Config.Serializers.MsSql
{
- public class DataTableDeserializer : IDeserializer
+ public class DataTableDeserializer : IDeserializer
{
+ private readonly ICryptographyProvider _cryptographyProvider;
+ private readonly SecureString _decryptionKey;
+
+ public DataTableDeserializer(ICryptographyProvider cryptographyProvider, SecureString decryptionKey)
+ {
+ _cryptographyProvider = cryptographyProvider.ThrowIfNull(nameof(cryptographyProvider));
+ _decryptionKey = decryptionKey.ThrowIfNull(nameof(decryptionKey));
+ }
+
public ConnectionTreeModel Deserialize(DataTable table)
{
var connectionList = CreateNodesFromTable(table);
@@ -34,10 +47,10 @@ namespace mRemoteNG.Config.Serializers
switch ((string)row["Type"])
{
case "Connection":
- nodeList.Add(DeserializeConnectionInfo(row));
+ nodeList.Add(DeserializeConnectionInfo(row));
break;
case "Container":
- nodeList.Add(DeserializeContainerInfo(row));
+ nodeList.Add(DeserializeContainerInfo(row));
break;
}
}
@@ -68,16 +81,12 @@ namespace mRemoteNG.Config.Serializers
// The Parent object is linked properly later in CreateNodeHierarchy()
//connectionInfo.Parent.ConstantID = (string)dataRow["ParentID"];
- var info = connectionInfo as ContainerInfo;
- if(info != null)
- info.IsExpanded = (bool)dataRow["Expanded"];
-
connectionInfo.Description = (string)dataRow["Description"];
connectionInfo.Icon = (string)dataRow["Icon"];
connectionInfo.Panel = (string)dataRow["Panel"];
connectionInfo.Username = (string)dataRow["Username"];
connectionInfo.Domain = (string)dataRow["DomainName"];
- connectionInfo.Password = (string)dataRow["Password"];
+ connectionInfo.Password = DecryptValue((string)dataRow["Password"]);
connectionInfo.Hostname = (string)dataRow["Hostname"];
connectionInfo.Protocol = (ProtocolType)Enum.Parse(typeof(ProtocolType), (string)dataRow["Protocol"]);
connectionInfo.PuttySession = (string)dataRow["PuttySession"];
@@ -106,7 +115,6 @@ namespace mRemoteNG.Config.Serializers
connectionInfo.RedirectSound = (RdpProtocol.RDPSounds)Enum.Parse(typeof(RdpProtocol.RDPSounds), (string)dataRow["RedirectSound"]);
connectionInfo.SoundQuality = (RdpProtocol.RDPSoundQuality)Enum.Parse(typeof(RdpProtocol.RDPSoundQuality), (string)dataRow["SoundQuality"]);
connectionInfo.RedirectKeys = (bool)dataRow["RedirectKeys"];
- connectionInfo.PleaseConnect = (bool)dataRow["Connected"];
connectionInfo.PreExtApp = (string)dataRow["PreExtApp"];
connectionInfo.PostExtApp = (string)dataRow["PostExtApp"];
connectionInfo.MacAddress = (string)dataRow["MacAddress"];
@@ -119,7 +127,7 @@ namespace mRemoteNG.Config.Serializers
connectionInfo.VNCProxyIP = (string)dataRow["VNCProxyIP"];
connectionInfo.VNCProxyPort = (int)dataRow["VNCProxyPort"];
connectionInfo.VNCProxyUsername = (string)dataRow["VNCProxyUsername"];
- connectionInfo.VNCProxyPassword = (string)dataRow["VNCProxyPassword"];
+ connectionInfo.VNCProxyPassword = DecryptValue((string)dataRow["VNCProxyPassword"]);
connectionInfo.VNCColors = (ProtocolVNC.Colors)Enum.Parse(typeof(ProtocolVNC.Colors), (string)dataRow["VNCColors"]);
connectionInfo.VNCSmartSizeMode = (ProtocolVNC.SmartSizeMode)Enum.Parse(typeof(ProtocolVNC.SmartSizeMode), (string)dataRow["VNCSmartSizeMode"]);
connectionInfo.VNCViewOnly = (bool)dataRow["VNCViewOnly"];
@@ -127,7 +135,7 @@ namespace mRemoteNG.Config.Serializers
connectionInfo.RDGatewayHostname = (string)dataRow["RDGatewayHostname"];
connectionInfo.RDGatewayUseConnectionCredentials = (RdpProtocol.RDGatewayUseConnectionCredentials)Enum.Parse(typeof(RdpProtocol.RDGatewayUseConnectionCredentials), (string)dataRow["RDGatewayUseConnectionCredentials"]);
connectionInfo.RDGatewayUsername = (string)dataRow["RDGatewayUsername"];
- connectionInfo.RDGatewayPassword = (string)dataRow["RDGatewayPassword"];
+ connectionInfo.RDGatewayPassword = DecryptValue((string)dataRow["RDGatewayPassword"]);
connectionInfo.RDGatewayDomain = (string)dataRow["RDGatewayDomain"];
connectionInfo.Inheritance.CacheBitmaps = (bool)dataRow["InheritCacheBitmaps"];
@@ -187,10 +195,26 @@ namespace mRemoteNG.Config.Serializers
connectionInfo.Inheritance.RDGatewayDomain = (bool)dataRow["InheritRDGatewayDomain"];
}
+ private string DecryptValue(string cipherText)
+ {
+ try
+ {
+ return _cryptographyProvider.Decrypt(cipherText, _decryptionKey);
+ }
+ catch (EncryptionException)
+ {
+ // value may not be encrypted
+ return cipherText;
+ }
+ }
+
private ConnectionTreeModel CreateNodeHierarchy(List connectionList, DataTable dataTable)
{
var connectionTreeModel = new ConnectionTreeModel();
- var rootNode = new RootNodeInfo(RootNodeType.Connection, "0");
+ var rootNode = new RootNodeInfo(RootNodeType.Connection, "0")
+ {
+ PasswordString = _decryptionKey.ConvertToUnsecureString()
+ };
connectionTreeModel.AddRootNode(rootNode);
foreach (DataRow row in dataTable.Rows)
diff --git a/mRemoteV1/Config/Serializers/DataTableSerializer.cs b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableSerializer.cs
similarity index 94%
rename from mRemoteV1/Config/Serializers/DataTableSerializer.cs
rename to mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableSerializer.cs
index 05fd2ba8..ca65f093 100644
--- a/mRemoteV1/Config/Serializers/DataTableSerializer.cs
+++ b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableSerializer.cs
@@ -1,25 +1,31 @@
-using System;
-using System.Data;
-using System.Data.SqlTypes;
-using System.Linq;
-using mRemoteNG.Connection;
+using mRemoteNG.Connection;
using mRemoteNG.Container;
using mRemoteNG.Security;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
+using System;
+using System.Data;
+using System.Data.SqlTypes;
+using System.Linq;
+using System.Security;
+using mRemoteNG.Tools;
-namespace mRemoteNG.Config.Serializers
+namespace mRemoteNG.Config.Serializers.MsSql
{
public class DataTableSerializer : ISerializer
{
+ private readonly ICryptographyProvider _cryptographyProvider;
+ private readonly SecureString _encryptionKey;
private DataTable _dataTable;
private const string TableName = "tblCons";
private readonly SaveFilter _saveFilter;
private int _currentNodeIndex;
- public DataTableSerializer(SaveFilter saveFilter)
+ public DataTableSerializer(SaveFilter saveFilter, ICryptographyProvider cryptographyProvider, SecureString encryptionKey)
{
- _saveFilter = saveFilter;
+ _saveFilter = saveFilter.ThrowIfNull(nameof(saveFilter));
+ _cryptographyProvider = cryptographyProvider.ThrowIfNull(nameof(cryptographyProvider));
+ _encryptionKey = encryptionKey.ThrowIfNull(nameof(encryptionKey));
}
@@ -206,14 +212,15 @@ namespace mRemoteNG.Config.Serializers
dataRow["ParentID"] = connectionInfo.Parent?.ConstantID ?? "";
dataRow["PositionID"] = _currentNodeIndex;
dataRow["LastChange"] = (SqlDateTime)DateTime.Now;
- var info = connectionInfo as ContainerInfo;
- dataRow["Expanded"] = info != null && info.IsExpanded;
+ 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["DomainName"] = _saveFilter.SaveDomain ? connectionInfo.Domain : "";
- dataRow["Password"] = _saveFilter.SavePassword ? connectionInfo.Password : "";
+ dataRow["Password"] = _saveFilter.SavePassword
+ ? _cryptographyProvider.Encrypt(connectionInfo.Password, _encryptionKey)
+ : "";
dataRow["Hostname"] = connectionInfo.Hostname;
dataRow["Protocol"] = connectionInfo.Protocol;
dataRow["PuttySession"] = connectionInfo.PuttySession;
@@ -242,7 +249,7 @@ namespace mRemoteNG.Config.Serializers
dataRow["RedirectSound"] = connectionInfo.RedirectSound;
dataRow["SoundQuality"] = connectionInfo.SoundQuality;
dataRow["RedirectKeys"] = connectionInfo.RedirectKeys;
- dataRow["Connected"] = connectionInfo.OpenConnections.Count > 0;
+ 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;
@@ -255,14 +262,14 @@ namespace mRemoteNG.Config.Serializers
dataRow["VNCProxyIP"] = connectionInfo.VNCProxyIP;
dataRow["VNCProxyPort"] = connectionInfo.VNCProxyPort;
dataRow["VNCProxyUsername"] = connectionInfo.VNCProxyUsername;
- dataRow["VNCProxyPassword"] = connectionInfo.VNCProxyPassword;
+ dataRow["VNCProxyPassword"] = _cryptographyProvider.Encrypt(connectionInfo.VNCProxyPassword, _encryptionKey);
dataRow["VNCColors"] = connectionInfo.VNCColors;
dataRow["VNCSmartSizeMode"] = connectionInfo.VNCSmartSizeMode;
dataRow["VNCViewOnly"] = connectionInfo.VNCViewOnly;
dataRow["RDGatewayUsageMethod"] = connectionInfo.RDGatewayUsageMethod;
dataRow["RDGatewayHostname"] = connectionInfo.RDGatewayHostname;
dataRow["RDGatewayUseConnectionCredentials"] = connectionInfo.RDGatewayUseConnectionCredentials;
- dataRow["RDGatewayUsername"] = connectionInfo.RDGatewayUsername;
+ dataRow["RDGatewayUsername"] = _cryptographyProvider.Encrypt(connectionInfo.RDGatewayUsername, _encryptionKey);
dataRow["RDGatewayPassword"] = connectionInfo.RDGatewayPassword;
dataRow["RDGatewayDomain"] = connectionInfo.RDGatewayDomain;
if (_saveFilter.SaveInheritance)
diff --git a/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/LocalConnectionPropertiesModel.cs b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/LocalConnectionPropertiesModel.cs
new file mode 100644
index 00000000..f8cdb35f
--- /dev/null
+++ b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/LocalConnectionPropertiesModel.cs
@@ -0,0 +1,20 @@
+namespace mRemoteNG.Config.Serializers.MsSql
+{
+ public class LocalConnectionPropertiesModel
+ {
+ ///
+ /// The unique Id of this tree node
+ ///
+ public string ConnectionId { get; set; }
+
+ ///
+ /// Indicates whether this connection is connected
+ ///
+ public bool Connected { get; set; }
+
+ ///
+ /// Indicates whether this container is expanded in the tree
+ ///
+ public bool Expanded { get; set; }
+ }
+}
diff --git a/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/LocalConnectionPropertiesXmlSerializer.cs b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/LocalConnectionPropertiesXmlSerializer.cs
new file mode 100644
index 00000000..78a82371
--- /dev/null
+++ b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/LocalConnectionPropertiesXmlSerializer.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+
+namespace mRemoteNG.Config.Serializers.MsSql
+{
+ public class LocalConnectionPropertiesXmlSerializer :
+ ISerializer, string>,
+ IDeserializer>
+ {
+ public string Serialize(IEnumerable models)
+ {
+ var localConnections = models
+ .Select(m => new XElement("Node",
+ new XAttribute("ConnectionId", m.ConnectionId),
+ new XAttribute("Connected", m.Connected),
+ new XAttribute("Expanded", m.Expanded)));
+
+ var root = new XElement("LocalConnections", localConnections);
+ var xdoc = new XDocument(new XDeclaration("1.0", "utf-8", null), root);
+ return WriteXmlToString(xdoc);
+ }
+
+ public IEnumerable Deserialize(string serializedData)
+ {
+ if (string.IsNullOrWhiteSpace(serializedData))
+ return Enumerable.Empty();
+
+ var xdoc = XDocument.Parse(serializedData);
+ return xdoc
+ .Descendants("Node")
+ .Where(e => e.Attribute("ConnectionId") != null)
+ .Select(e => new LocalConnectionPropertiesModel
+ {
+ ConnectionId = e.Attribute("ConnectionId")?.Value,
+ Connected = bool.Parse(e.Attribute("Connected")?.Value ?? "False"),
+ Expanded = bool.Parse(e.Attribute("Expanded")?.Value ?? "False")
+ });
+ }
+
+ private static string WriteXmlToString(XNode xmlDocument)
+ {
+ string xmlString;
+ var xmlWriterSettings = new XmlWriterSettings { Indent = true, IndentChars = " ", Encoding = Encoding.UTF8 };
+ var memoryStream = new MemoryStream();
+ using (var xmlTextWriter = XmlWriter.Create(memoryStream, xmlWriterSettings))
+ {
+ xmlDocument.WriteTo(xmlTextWriter);
+ xmlTextWriter.Flush();
+ var streamReader = new StreamReader(memoryStream, Encoding.UTF8, true);
+ memoryStream.Seek(0, SeekOrigin.Begin);
+ xmlString = streamReader.ReadToEnd();
+ }
+ return xmlString;
+ }
+ }
+}
diff --git a/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/SqlConnectionListMetaData.cs b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/SqlConnectionListMetaData.cs
new file mode 100644
index 00000000..12e8ce86
--- /dev/null
+++ b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/SqlConnectionListMetaData.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Security;
+
+namespace mRemoteNG.Config.Serializers.MsSql
+{
+ public class SqlConnectionListMetaData
+ {
+ public string Name { get; set; }
+ public string Protected { get; set; }
+ public bool Export { get; set; }
+ public Version ConfVersion { get; set; }
+ }
+}
diff --git a/mRemoteV1/Config/Serializers/Versioning/SqlDatabaseVersionRetriever.cs b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/SqlDatabaseMetaDataRetriever.cs
similarity index 57%
rename from mRemoteV1/Config/Serializers/Versioning/SqlDatabaseVersionRetriever.cs
rename to mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/SqlDatabaseMetaDataRetriever.cs
index 61c1a778..8c97292d 100644
--- a/mRemoteV1/Config/Serializers/Versioning/SqlDatabaseVersionRetriever.cs
+++ b/mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/SqlDatabaseMetaDataRetriever.cs
@@ -5,13 +5,13 @@ using mRemoteNG.App;
using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Messages;
-namespace mRemoteNG.Config.Serializers.Versioning
+namespace mRemoteNG.Config.Serializers.MsSql
{
- public class SqlDatabaseVersionRetriever
+ public class SqlDatabaseMetaDataRetriever
{
- public Version GetDatabaseVersion(SqlDatabaseConnector sqlDatabaseConnector)
+ public SqlConnectionListMetaData GetDatabaseMetaData(SqlDatabaseConnector sqlDatabaseConnector)
{
- Version databaseVersion;
+ SqlConnectionListMetaData metaData;
SqlDataReader sqlDataReader = null;
try
{
@@ -20,10 +20,17 @@ namespace mRemoteNG.Config.Serializers.Versioning
sqlDatabaseConnector.Connect();
sqlDataReader = sqlCommand.ExecuteReader();
if (!sqlDataReader.HasRows)
- return new Version(); // assume new empty database
+ return null; // assume new empty database
else
sqlDataReader.Read();
- databaseVersion = new Version(Convert.ToString(sqlDataReader["confVersion"], CultureInfo.InvariantCulture));
+
+ metaData = new SqlConnectionListMetaData
+ {
+ Name = sqlDataReader["Name"] as string ?? "",
+ Protected = sqlDataReader["Protected"] as string ?? "",
+ Export = (bool)sqlDataReader["Export"],
+ ConfVersion = new Version(Convert.ToString(sqlDataReader["confVersion"], CultureInfo.InvariantCulture))
+ };
}
catch (Exception ex)
{
@@ -35,7 +42,7 @@ namespace mRemoteNG.Config.Serializers.Versioning
if (sqlDataReader != null && !sqlDataReader.IsClosed)
sqlDataReader.Close();
}
- return databaseVersion;
+ return metaData;
}
}
}
\ No newline at end of file
diff --git a/mRemoteV1/Config/Serializers/Versioning/SqlDatabaseVersionVerifier.cs b/mRemoteV1/Config/Serializers/Versioning/SqlDatabaseVersionVerifier.cs
index a2ede1bb..b269f27e 100644
--- a/mRemoteV1/Config/Serializers/Versioning/SqlDatabaseVersionVerifier.cs
+++ b/mRemoteV1/Config/Serializers/Versioning/SqlDatabaseVersionVerifier.cs
@@ -9,7 +9,6 @@ namespace mRemoteNG.Config.Serializers.Versioning
public class SqlDatabaseVersionVerifier
{
private readonly SqlDatabaseConnector _sqlDatabaseConnector;
- private readonly SqlDatabaseVersionRetriever _versionRetriever;
public SqlDatabaseVersionVerifier(SqlDatabaseConnector sqlDatabaseConnector)
{
@@ -17,15 +16,14 @@ namespace mRemoteNG.Config.Serializers.Versioning
throw new ArgumentNullException(nameof(sqlDatabaseConnector));
_sqlDatabaseConnector = sqlDatabaseConnector;
- _versionRetriever = new SqlDatabaseVersionRetriever();
}
- public bool VerifyDatabaseVersion()
+ public bool VerifyDatabaseVersion(Version dbVersion)
{
var isVerified = false;
try
{
- var databaseVersion = _versionRetriever.GetDatabaseVersion(_sqlDatabaseConnector);
+ var databaseVersion = dbVersion;
if (databaseVersion.Equals(new Version()))
{
diff --git a/mRemoteV1/Connection/AbstractConnectionRecord.cs b/mRemoteV1/Connection/AbstractConnectionRecord.cs
index 835bd9a6..a7f8c6fb 100644
--- a/mRemoteV1/Connection/AbstractConnectionRecord.cs
+++ b/mRemoteV1/Connection/AbstractConnectionRecord.cs
@@ -685,7 +685,7 @@ namespace mRemoteNG.Connection
PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(args.PropertyName));
}
- private void SetField(ref T field, T value, string propertyName = null)
+ protected void SetField(ref T field, T value, string propertyName = null)
{
if (EqualityComparer.Default.Equals(field, value)) return;
field = value;
diff --git a/mRemoteV1/Connection/ConnectionInitiator.cs b/mRemoteV1/Connection/ConnectionInitiator.cs
index 19a8813b..14f6fa77 100644
--- a/mRemoteV1/Connection/ConnectionInitiator.cs
+++ b/mRemoteV1/Connection/ConnectionInitiator.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Windows.Forms;
-using mRemoteNG.App;
+using mRemoteNG.App;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Connection.Protocol.RDP;
using mRemoteNG.Container;
@@ -8,14 +6,23 @@ using mRemoteNG.Messages;
using mRemoteNG.UI.Forms;
using mRemoteNG.UI.Panels;
using mRemoteNG.UI.Window;
+using System;
+using System.Collections.Generic;
+using System.Windows.Forms;
using TabPage = Crownwood.Magic.Controls.TabPage;
namespace mRemoteNG.Connection
{
- public class ConnectionInitiator : IConnectionInitiator
+ public class ConnectionInitiator : IConnectionInitiator
{
private readonly PanelAdder _panelAdder = new PanelAdder();
+ private readonly List _activeConnections = new List();
+
+ ///
+ /// List of unique IDs of the currently active connections
+ ///
+ public IEnumerable ActiveConnections => _activeConnections;
public void OpenConnection(ContainerInfo containerInfo, ConnectionInfo.Force force = ConnectionInfo.Force.None)
{
@@ -118,6 +125,7 @@ namespace mRemoteNG.Connection
}
connectionInfo.OpenConnections.Add(newProtocol);
+ _activeConnections.Add(connectionInfo.ConstantID);
FrmMain.Default.SelectedConnection = connectionInfo;
}
catch (Exception ex)
@@ -210,7 +218,7 @@ namespace mRemoteNG.Connection
newProtocol.Closed += ((ConnectionWindow)connectionForm).Prot_Event_Closed;
}
- private static void SetConnectionEventHandlers(ProtocolBase newProtocol)
+ private void SetConnectionEventHandlers(ProtocolBase newProtocol)
{
newProtocol.Disconnected += Prot_Event_Disconnected;
newProtocol.Connected += Prot_Event_Connected;
@@ -251,7 +259,7 @@ namespace mRemoteNG.Connection
}
}
- private static void Prot_Event_Closed(object sender)
+ private void Prot_Event_Closed(object sender)
{
try
{
@@ -267,6 +275,8 @@ namespace mRemoteNG.Connection
Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, string.Format(Language.strConnenctionClosedByUser, connDetail, prot.InterfaceControl.Info.Protocol, Environment.UserName));
prot.InterfaceControl.Info.OpenConnections.Remove(prot);
+ if (_activeConnections.Contains(prot.InterfaceControl.Info.ConstantID))
+ _activeConnections.Remove(prot.InterfaceControl.Info.ConstantID);
if (prot.InterfaceControl.Info.PostExtApp == "") return;
var extA = Runtime.ExternalToolsService.GetExtAppByName(prot.InterfaceControl.Info.PostExtApp);
diff --git a/mRemoteV1/Connection/ConnectionsService.cs b/mRemoteV1/Connection/ConnectionsService.cs
index 0fcbdeae..5fe7c855 100644
--- a/mRemoteV1/Connection/ConnectionsService.cs
+++ b/mRemoteV1/Connection/ConnectionsService.cs
@@ -1,12 +1,10 @@
-using System;
-using System.IO;
-using System.Threading;
-using System.Windows.Forms;
-using mRemoteNG.App;
+using mRemoteNG.App;
using mRemoteNG.App.Info;
using mRemoteNG.Config.Connections;
using mRemoteNG.Config.Connections.Multiuser;
+using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Putty;
+using mRemoteNG.Config.Serializers.MsSql;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Messages;
using mRemoteNG.Security;
@@ -14,6 +12,11 @@ using mRemoteNG.Tools;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
using mRemoteNG.UI;
+using System;
+using System.IO;
+using System.Threading;
+using System.Windows.Forms;
+using mRemoteNG.Config;
namespace mRemoteNG.Connection
{
@@ -21,6 +24,8 @@ namespace mRemoteNG.Connection
{
private static readonly object SaveLock = new object();
private readonly PuttySessionsManager _puttySessionsManager;
+ private readonly IDataProvider _localConnectionPropertiesDataProvider;
+ private readonly LocalConnectionPropertiesXmlSerializer _localConnectionPropertiesSerializer;
private bool _batchingSaves = false;
private bool _saveRequested = false;
private bool _saveAsyncRequested = false;
@@ -39,6 +44,9 @@ namespace mRemoteNG.Connection
throw new ArgumentNullException(nameof(puttySessionsManager));
_puttySessionsManager = puttySessionsManager;
+ var path = SettingsFileInfo.SettingsPath;
+ _localConnectionPropertiesDataProvider = new FileDataProvider(Path.Combine(path, "LocalConnectionProperties.xml"));
+ _localConnectionPropertiesSerializer = new LocalConnectionPropertiesXmlSerializer();
}
public void NewConnectionsFile(string filename)
@@ -107,9 +115,14 @@ namespace mRemoteNG.Connection
var oldConnectionTreeModel = ConnectionTreeModel;
var oldIsUsingDatabaseValue = UsingDatabase;
- var newConnectionTreeModel = useDatabase
- ? new SqlConnectionsLoader().Load()
- : new XmlConnectionsLoader(connectionFileName).Load();
+ var connectionLoader = useDatabase
+ ? (IConnectionsLoader)new SqlConnectionsLoader(_localConnectionPropertiesSerializer, _localConnectionPropertiesDataProvider)
+ : new XmlConnectionsLoader(connectionFileName);
+
+ var newConnectionTreeModel = connectionLoader.Load();
+
+ if (useDatabase)
+ LastSqlUpdate = DateTime.Now;
if (newConnectionTreeModel == null)
{
@@ -130,6 +143,7 @@ namespace mRemoteNG.Connection
ConnectionTreeModel = newConnectionTreeModel;
UpdateCustomConsPathSetting(connectionFileName);
RaiseConnectionsLoadedEvent(oldConnectionTreeModel, newConnectionTreeModel, oldIsUsingDatabaseValue, useDatabase, connectionFileName);
+ Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Connections loaded using {connectionLoader.GetType().Name}");
}
///
@@ -176,7 +190,17 @@ namespace mRemoteNG.Connection
///
///
/// Bypasses safety checks that prevent saving if a connection file isn't loaded.
- public void SaveConnections(ConnectionTreeModel connectionTreeModel, bool useDatabase, SaveFilter saveFilter, string connectionFileName, bool forceSave = false)
+ ///
+ /// Optional. The name of the property that triggered
+ /// this save.
+ ///
+ public void SaveConnections(
+ ConnectionTreeModel connectionTreeModel,
+ bool useDatabase,
+ SaveFilter saveFilter,
+ string connectionFileName,
+ bool forceSave = false,
+ string propertyNameTrigger = "")
{
if (connectionTreeModel == null)
return;
@@ -196,10 +220,13 @@ namespace mRemoteNG.Connection
RemoteConnectionsSyncronizer?.Disable();
var previouslyUsingDatabase = UsingDatabase;
- if (useDatabase)
- new SqlConnectionsSaver(saveFilter).Save(connectionTreeModel);
- else
- new XmlConnectionsSaver(connectionFileName, saveFilter).Save(connectionTreeModel);
+
+ var saver = useDatabase
+ ? (ISaver)new SqlConnectionsSaver(saveFilter, _localConnectionPropertiesSerializer,
+ _localConnectionPropertiesDataProvider)
+ : new XmlConnectionsSaver(connectionFileName, saveFilter);
+
+ saver.Save(connectionTreeModel, propertyNameTrigger);
if (UsingDatabase)
LastSqlUpdate = DateTime.Now;
@@ -219,7 +246,14 @@ namespace mRemoteNG.Connection
}
}
- public void SaveConnectionsAsync()
+ ///
+ /// Save the currently loaded connections asynchronously
+ ///
+ ///
+ /// Optional. The name of the property that triggered
+ /// this save.
+ ///
+ public void SaveConnectionsAsync(string propertyNameTrigger = "")
{
if (_batchingSaves)
{
@@ -227,19 +261,22 @@ namespace mRemoteNG.Connection
return;
}
- var t = new Thread(SaveConnectionsBGd);
+ var t = new Thread(() =>
+ {
+ lock (SaveLock)
+ {
+ SaveConnections(
+ ConnectionTreeModel,
+ UsingDatabase,
+ new SaveFilter(),
+ ConnectionFileName,
+ propertyNameTrigger: propertyNameTrigger);
+ }
+ });
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
- private void SaveConnectionsBGd()
- {
- lock (SaveLock)
- {
- SaveConnections();
- }
- }
-
public string GetStartupConnectionFileName()
{
return Settings.Default.LoadConsFromCustomLocation == false
diff --git a/mRemoteV1/Connection/IConnectionInitiator.cs b/mRemoteV1/Connection/IConnectionInitiator.cs
index 5a0ff1d3..485a8940 100644
--- a/mRemoteV1/Connection/IConnectionInitiator.cs
+++ b/mRemoteV1/Connection/IConnectionInitiator.cs
@@ -1,9 +1,12 @@
using mRemoteNG.Container;
+using System.Collections.Generic;
namespace mRemoteNG.Connection
{
public interface IConnectionInitiator
{
+ IEnumerable ActiveConnections { get; }
+
void OpenConnection(ConnectionInfo connectionInfo);
void OpenConnection(ContainerInfo containerInfo, ConnectionInfo.Force force = ConnectionInfo.Force.None);
diff --git a/mRemoteV1/Container/ContainerInfo.cs b/mRemoteV1/Container/ContainerInfo.cs
index c6c6b4c8..989fc911 100644
--- a/mRemoteV1/Container/ContainerInfo.cs
+++ b/mRemoteV1/Container/ContainerInfo.cs
@@ -12,13 +12,19 @@ namespace mRemoteNG.Container
[DefaultProperty("Name")]
public class ContainerInfo : ConnectionInfo, INotifyCollectionChanged
{
- [Browsable(false)]
+ private bool _isExpanded;
+
+ [Browsable(false)]
public List Children { get; } = new List();
- [Category(""), Browsable(false), ReadOnly(false), Bindable(false), DefaultValue(""), DesignOnly(false)]
- public bool IsExpanded { get; set; }
+ [Category(""), Browsable(false), ReadOnly(false), Bindable(false), DefaultValue(""), DesignOnly(false)]
+ public bool IsExpanded
+ {
+ get => _isExpanded;
+ set => SetField(ref _isExpanded, value, "IsExpanded");
+ }
- [Browsable(false)]
+ [Browsable(false)]
public override bool IsContainer { get { return true; } set {} }
public ContainerInfo(string uniqueId)
diff --git a/mRemoteV1/Tools/Extensions.cs b/mRemoteV1/Tools/Extensions.cs
index 76bcba77..ad4eb66b 100644
--- a/mRemoteV1/Tools/Extensions.cs
+++ b/mRemoteV1/Tools/Extensions.cs
@@ -1,9 +1,11 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
namespace mRemoteNG.Tools
{
- public static class Extensions
+ public static class Extensions
{
public static Optional Maybe(this T value)
{
@@ -50,5 +52,23 @@ namespace mRemoteNG.Tools
throw new ArgumentException("Value cannot be null or empty", argName);
return value;
}
+
+ ///
+ /// Perform an action for each item in the given collection. The item
+ /// is the pass along the processing chain.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable ForEach(this IEnumerable collection, Action action)
+ {
+ collection = collection.ToList();
+
+ foreach (var item in collection)
+ action(item);
+
+ return collection;
+ }
}
}
diff --git a/mRemoteV1/Tree/PreviousSessionOpener.cs b/mRemoteV1/Tree/PreviousSessionOpener.cs
index 5df20c8a..ff664b98 100644
--- a/mRemoteV1/Tree/PreviousSessionOpener.cs
+++ b/mRemoteV1/Tree/PreviousSessionOpener.cs
@@ -1,8 +1,8 @@
-using System;
-using System.Linq;
-using mRemoteNG.Connection;
+using mRemoteNG.Connection;
using mRemoteNG.Container;
using mRemoteNG.UI.Controls;
+using System;
+using System.Linq;
namespace mRemoteNG.Tree
@@ -21,7 +21,12 @@ namespace mRemoteNG.Tree
public void Execute(IConnectionTree connectionTree)
{
var connectionInfoList = connectionTree.GetRootConnectionNode().GetRecursiveChildList().Where(node => !(node is ContainerInfo));
- var previouslyOpenedConnections = connectionInfoList.Where(item => item.PleaseConnect);
+ var previouslyOpenedConnections = connectionInfoList
+ .Where(item =>
+ item.PleaseConnect &&
+ // ignore items that have already connected
+ !_connectionInitiator.ActiveConnections.Contains(item.ConstantID));
+
foreach (var connectionInfo in previouslyOpenedConnections)
{
_connectionInitiator.OpenConnection(connectionInfo);
diff --git a/mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs b/mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs
index 1919b6c7..14615af5 100644
--- a/mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs
+++ b/mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs
@@ -1,16 +1,16 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.ComponentModel;
-using System.Linq;
-using System.Windows.Forms;
-using BrightIdeasSoftware;
+using BrightIdeasSoftware;
using mRemoteNG.App;
using mRemoteNG.Config.Putty;
using mRemoteNG.Connection;
using mRemoteNG.Container;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows.Forms;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.UI.Controls
@@ -43,8 +43,12 @@ namespace mRemoteNG.UI.Controls
get { return _connectionTreeModel; }
set
{
+ if (_connectionTreeModel == value)
+ return;
+
+ UnregisterModelUpdateHandlers(_connectionTreeModel);
_connectionTreeModel = value;
- PopulateTreeView();
+ PopulateTreeView(value);
}
}
@@ -156,28 +160,31 @@ namespace mRemoteNG.UI.Controls
padding;
}
- private void PopulateTreeView()
+ private void PopulateTreeView(ConnectionTreeModel newModel)
{
- UnregisterModelUpdateHandlers();
- SetObjects(ConnectionTreeModel.RootNodes);
- RegisterModelUpdateHandlers();
- NodeSearcher = new NodeSearcher(ConnectionTreeModel);
+ SetObjects(newModel.RootNodes);
+ RegisterModelUpdateHandlers(newModel);
+ NodeSearcher = new NodeSearcher(newModel);
ExecutePostSetupActions();
AutoResizeColumn(Columns[0]);
}
- private void RegisterModelUpdateHandlers()
+ private void RegisterModelUpdateHandlers(ConnectionTreeModel newModel)
{
_puttySessionsManager.PuttySessionsCollectionChanged += OnPuttySessionsCollectionChanged;
- ConnectionTreeModel.CollectionChanged += HandleCollectionChanged;
- ConnectionTreeModel.PropertyChanged += HandleCollectionPropertyChanged;
+ newModel.CollectionChanged += HandleCollectionChanged;
+ newModel.PropertyChanged += HandleCollectionPropertyChanged;
}
- private void UnregisterModelUpdateHandlers()
+ private void UnregisterModelUpdateHandlers(ConnectionTreeModel oldConnectionTreeModel)
{
_puttySessionsManager.PuttySessionsCollectionChanged -= OnPuttySessionsCollectionChanged;
- ConnectionTreeModel.CollectionChanged -= HandleCollectionChanged;
- ConnectionTreeModel.PropertyChanged -= HandleCollectionPropertyChanged;
+
+ if (oldConnectionTreeModel == null)
+ return;
+
+ oldConnectionTreeModel.CollectionChanged -= HandleCollectionChanged;
+ oldConnectionTreeModel.PropertyChanged -= HandleCollectionPropertyChanged;
}
private void OnPuttySessionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
diff --git a/mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs b/mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs
index ad59e290..71022a18 100644
--- a/mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs
+++ b/mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs
@@ -79,7 +79,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
{
Runtime.ConnectionsService.RemoteConnectionsSyncronizer?.Dispose();
Runtime.ConnectionsService.RemoteConnectionsSyncronizer = new RemoteConnectionsSyncronizer(new SqlConnectionsUpdateChecker());
- Runtime.ConnectionsService.RemoteConnectionsSyncronizer.Enable();
+ Runtime.ConnectionsService.LoadConnections(true, false, "");
}
private void DisableSql()
diff --git a/mRemoteV1/UI/Forms/frmMain.cs b/mRemoteV1/UI/Forms/frmMain.cs
index 4be80b35..bb213821 100644
--- a/mRemoteV1/UI/Forms/frmMain.cs
+++ b/mRemoteV1/UI/Forms/frmMain.cs
@@ -429,7 +429,8 @@ namespace mRemoteNG.UI.Forms
// Only handle this msg if it was triggered by a click
if (NativeMethods.LOWORD(m.WParam) == NativeMethods.WA_CLICKACTIVE)
{
- var controlThatWasClicked = FromChildHandle(NativeMethods.WindowFromPoint(MousePosition));
+ var controlThatWasClicked = FromChildHandle(NativeMethods.WindowFromPoint(MousePosition))
+ ?? GetChildAtPoint(MousePosition);
if (controlThatWasClicked != null)
{
if (controlThatWasClicked is TreeView ||
@@ -444,9 +445,14 @@ namespace mRemoteNG.UI.Forms
controlThatWasClicked is Crownwood.Magic.Controls.TabControl ||
controlThatWasClicked is Crownwood.Magic.Controls.InertButton)
{
- // Simulate a mouse event since one wasn't generated by Windows
- SimulateClick(controlThatWasClicked);
- controlThatWasClicked.Focus();
+ // Simulate a mouse event since one wasn't generated by Windows
+ SimulateClick(controlThatWasClicked);
+ controlThatWasClicked.Focus();
+ }
+ else if (controlThatWasClicked is AutoHideStripBase)
+ {
+ // only focus the autohide toolstrip
+ controlThatWasClicked.Focus();
}
else
{
diff --git a/mRemoteV1/UI/Window/ConnectionTreeWindow.cs b/mRemoteV1/UI/Window/ConnectionTreeWindow.cs
index cb42408a..e5976d13 100644
--- a/mRemoteV1/UI/Window/ConnectionTreeWindow.cs
+++ b/mRemoteV1/UI/Window/ConnectionTreeWindow.cs
@@ -1,9 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Drawing;
-using System.Linq;
-using System.Windows.Forms;
using mRemoteNG.App;
using mRemoteNG.Config.Connections;
using mRemoteNG.Connection;
@@ -11,6 +5,12 @@ using mRemoteNG.Themes;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
using mRemoteNG.UI.Controls;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Linq;
+using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;
// ReSharper disable ArrangeAccessorOwnerBody
@@ -50,8 +50,6 @@ namespace mRemoteNG.UI.Window
ConnectionTree.UseFiltering = Settings.Default.UseFilterSearch;
ApplyFiltering();
}
-
- SetConnectionTreeEventHandlers();
}
diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj
index a9021b2d..e97f404c 100644
--- a/mRemoteV1/mRemoteV1.csproj
+++ b/mRemoteV1/mRemoteV1.csproj
@@ -136,6 +136,7 @@
+
@@ -160,6 +161,9 @@
+
+
+
@@ -170,14 +174,14 @@
-
-
+
+
-
+