Merge branch 'release/v1.76' into develop

# Conflicts:
#	CHANGELOG.TXT
#	mRemoteV1/Properties/AssemblyInfo.cs
#	mRemoteV1/mRemoteV1.csproj
This commit is contained in:
David Sparer
2018-11-08 16:38:51 -06:00
35 changed files with 581 additions and 192 deletions

View File

@@ -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:

View File

@@ -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);
}

View File

@@ -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]

View File

@@ -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";

View File

@@ -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();
}
/// <summary>
///
/// </summary>
/// <param name="withDialog">
/// Should we show the file selection dialog to allow the user to select
/// a connection file
/// </param>
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);

View File

@@ -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);

View File

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

View File

@@ -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

View File

@@ -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()

View File

@@ -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);
}
}
}

View File

@@ -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<string, IEnumerable<LocalConnectionPropertiesModel>> _localConnectionPropertiesDeserializer;
private readonly IDataProvider<string> _dataProvider;
public Func<Optional<SecureString>> AuthenticationRequestor { get; set; } =
() => MiscTools.PasswordDialog("", false);
public SqlConnectionsLoader(
IDeserializer<string, IEnumerable<LocalConnectionPropertiesModel>> localConnectionPropertiesDeserializer,
IDataProvider<string> 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<SecureString> 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<SecureString>.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;
});
}
}
}

View File

@@ -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<IEnumerable<LocalConnectionPropertiesModel>, string> _localPropertiesSerializer;
private readonly IDataProvider<string> _dataProvider;
public SqlConnectionsSaver(SaveFilter saveFilter)
public SqlConnectionsSaver(
SaveFilter saveFilter,
ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> localPropertieSerializer,
IDataProvider<string> 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<RootNodeInfo>().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<RootNodeInfo>().First();
UpdateRootNodeTable(rootTreeNode, sqlConnector);
UpdateConnectionsTable(rootTreeNode, sqlConnector);
UpdateUpdatesTable(sqlConnector);
}
Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Saved connections to database");
}
/// <summary>
/// Determines if a given property name should be only saved
/// locally.
/// </summary>
/// <param name="property">
/// The name of the property that triggered the save event
/// </param>
/// <returns></returns>
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);
}

View File

@@ -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;

View File

@@ -27,7 +27,7 @@ namespace mRemoteNG.Config.Connections
_saveFilter = saveFilter;
}
public void Save(ConnectionTreeModel connectionTreeModel)
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
try
{

View File

@@ -18,7 +18,7 @@ namespace mRemoteNG.Config
_dataProvider = dataProvider;
}
public void Save(IEnumerable<ICredentialRepository> repositories)
public void Save(IEnumerable<ICredentialRepository> repositories, string propertyNameTrigger = "")
{
var serializer = new CredentialRepositoryListSerializer();
var data = serializer.Serialize(repositories);

View File

@@ -2,6 +2,6 @@
{
public interface ISaver<in T>
{
void Save(T model);
void Save(T model, string propertyNameTrigger = "");
}
}

View File

@@ -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<DataTable, ConnectionTreeModel>
public class DataTableDeserializer : IDeserializer<DataTable, ConnectionTreeModel>
{
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<ConnectionInfo> 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)

View File

@@ -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<ConnectionInfo,DataTable>
{
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)

View File

@@ -0,0 +1,20 @@
namespace mRemoteNG.Config.Serializers.MsSql
{
public class LocalConnectionPropertiesModel
{
/// <summary>
/// The unique Id of this tree node
/// </summary>
public string ConnectionId { get; set; }
/// <summary>
/// Indicates whether this connection is connected
/// </summary>
public bool Connected { get; set; }
/// <summary>
/// Indicates whether this container is expanded in the tree
/// </summary>
public bool Expanded { get; set; }
}
}

View File

@@ -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<IEnumerable<LocalConnectionPropertiesModel>, string>,
IDeserializer<string, IEnumerable<LocalConnectionPropertiesModel>>
{
public string Serialize(IEnumerable<LocalConnectionPropertiesModel> 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<LocalConnectionPropertiesModel> Deserialize(string serializedData)
{
if (string.IsNullOrWhiteSpace(serializedData))
return Enumerable.Empty<LocalConnectionPropertiesModel>();
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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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()))
{

View File

@@ -685,7 +685,7 @@ namespace mRemoteNG.Connection
PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(args.PropertyName));
}
private void SetField<T>(ref T field, T value, string propertyName = null)
protected void SetField<T>(ref T field, T value, string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return;
field = value;

View File

@@ -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<string> _activeConnections = new List<string>();
/// <summary>
/// List of unique IDs of the currently active connections
/// </summary>
public IEnumerable<string> 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);

View File

@@ -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<string> _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}");
}
/// <summary>
@@ -176,7 +190,17 @@ namespace mRemoteNG.Connection
/// <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>
public void SaveConnections(ConnectionTreeModel connectionTreeModel, bool useDatabase, SaveFilter saveFilter, string connectionFileName, bool forceSave = false)
/// <param name="propertyNameTrigger">
/// 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 = "")
{
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<ConnectionTreeModel>)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()
/// <summary>
/// Save the currently loaded connections asynchronously
/// </summary>
/// <param name="propertyNameTrigger">
/// Optional. The name of the property that triggered
/// this save.
/// </param>
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

View File

@@ -1,9 +1,12 @@
using mRemoteNG.Container;
using System.Collections.Generic;
namespace mRemoteNG.Connection
{
public interface IConnectionInitiator
{
IEnumerable<string> ActiveConnections { get; }
void OpenConnection(ConnectionInfo connectionInfo);
void OpenConnection(ContainerInfo containerInfo, ConnectionInfo.Force force = ConnectionInfo.Force.None);

View File

@@ -12,13 +12,19 @@ namespace mRemoteNG.Container
[DefaultProperty("Name")]
public class ContainerInfo : ConnectionInfo, INotifyCollectionChanged
{
[Browsable(false)]
private bool _isExpanded;
[Browsable(false)]
public List<ConnectionInfo> Children { get; } = new List<ConnectionInfo>();
[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)

View File

@@ -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<T> Maybe<T>(this T value)
{
@@ -50,5 +52,23 @@ namespace mRemoteNG.Tools
throw new ArgumentException("Value cannot be null or empty", argName);
return value;
}
/// <summary>
/// Perform an action for each item in the given collection. The item
/// is the pass along the processing chain.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="collection"></param>
/// <param name="action"></param>
/// <returns></returns>
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> collection, Action<T> action)
{
collection = collection.ToList();
foreach (var item in collection)
action(item);
return collection;
}
}
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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()

View File

@@ -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
{

View File

@@ -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();
}

View File

@@ -136,6 +136,7 @@
<Compile Include="Config\Connections\ConnectionsLoadedEventArgs.cs" />
<Compile Include="Config\Connections\ConnectionsSavedEventArgs.cs" />
<Compile Include="Config\Connections\CsvConnectionsSaver.cs" />
<Compile Include="Config\Connections\IConnectionsLoader.cs" />
<Compile Include="Config\Connections\SaveConnectionsOnEdit.cs" />
<Compile Include="Config\Connections\SaveFormat.cs" />
<Compile Include="Config\Connections\Multiuser\ConnectionsUpdateCheckFinishedEventArgs.cs" />
@@ -160,6 +161,9 @@
<Compile Include="Config\Import\MRemoteNGCsvImporter.cs" />
<Compile Include="Config\ISaver.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\Xml\XmlConnectionNodeSerializer27.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\MsSql\LocalConnectionPropertiesModel.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\MsSql\LocalConnectionPropertiesXmlSerializer.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\MsSql\SqlConnectionListMetaData.cs" />
<Compile Include="Config\Serializers\CredentialProviderSerializer\CredentialRepositoryListDeserializer.cs" />
<Compile Include="Config\CredentialRepositoryListLoader.cs" />
<Compile Include="Config\Serializers\CredentialSerializer\XmlCredentialPasswordDecryptorDecorator.cs" />
@@ -170,14 +174,14 @@
<Compile Include="Config\Serializers\CredentialProviderSerializer\CredentialRepositoryListSerializer.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\Csv\CsvConnectionsDeserializerMremotengFormat.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\Csv\CsvConnectionsSerializerMremotengFormat.cs" />
<Compile Include="Config\Serializers\DataTableDeserializer.cs" />
<Compile Include="Config\Serializers\DataTableSerializer.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\MsSql\DataTableDeserializer.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\MsSql\DataTableSerializer.cs" />
<Compile Include="Config\Serializers\MiscSerializers\PortScanDeserializer.cs" />
<Compile Include="Config\Serializers\MiscSerializers\PuttyConnectionManagerDeserializer.cs" />
<Compile Include="Config\Serializers\MiscSerializers\RemoteDesktopConnectionDeserializer.cs" />
<Compile Include="Config\Serializers\MiscSerializers\RemoteDesktopConnectionManagerDeserializer.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\Xml\XmlConnectionNodeSerializer26.cs" />
<Compile Include="Config\Serializers\Versioning\SqlDatabaseVersionRetriever.cs" />
<Compile Include="Config\Serializers\ConnectionSerializers\MsSql\SqlDatabaseMetaDataRetriever.cs" />
<Compile Include="Config\Serializers\Versioning\IVersionUpgrader.cs" />
<Compile Include="Config\Serializers\Versioning\SqlVersion22To23Upgrader.cs" />
<Compile Include="Config\Serializers\Versioning\SqlVersion23To24Upgrader.cs" />