mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-17 22:11:48 +08:00
Compare commits
80 Commits
2022.01.07
...
reapply_cr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a5f0f248e | ||
|
|
c1931ff4cd | ||
|
|
12cb7ad7b0 | ||
|
|
4349b1b65b | ||
|
|
bb04605dc3 | ||
|
|
294bd2f7a4 | ||
|
|
a3aa323cce | ||
|
|
355cd63acb | ||
|
|
93d50b0818 | ||
|
|
8779776ad5 | ||
|
|
3bb285d180 | ||
|
|
052796c794 | ||
|
|
33a6dbb4c3 | ||
|
|
953ddaa292 | ||
|
|
c065b86dbd | ||
|
|
95859b96ff | ||
|
|
ba62c17ea2 | ||
|
|
1f296f2f72 | ||
|
|
89bb4d45b3 | ||
|
|
6e417ed47e | ||
|
|
f3a7d97016 | ||
|
|
530a4e165d | ||
|
|
9e217dba79 | ||
|
|
d524df6315 | ||
|
|
5e224f5b5b | ||
|
|
3649927618 | ||
|
|
4b736176bf | ||
|
|
f78ca1e9fd | ||
|
|
739112a3ff | ||
|
|
923b9efd40 | ||
|
|
f0d0b5ae21 | ||
|
|
8906e08704 | ||
|
|
9ead7e8e16 | ||
|
|
788ca79ece | ||
|
|
88558a353c | ||
|
|
7fd9abbbc8 | ||
|
|
b029b35df7 | ||
|
|
187ca5e55b | ||
|
|
53c26d8a91 | ||
|
|
159f25b8a1 | ||
|
|
f4904f350e | ||
|
|
ea59f6ad9d | ||
|
|
4bba05f737 | ||
|
|
569cf38f24 | ||
|
|
6f8cde4d8e | ||
|
|
753ea9b421 | ||
|
|
a5ea867b6d | ||
|
|
80228966f9 | ||
|
|
b8298ecb14 | ||
|
|
ee1db6ff8b | ||
|
|
aab1fb1dd2 | ||
|
|
fbea9d1ede | ||
|
|
75cfc9c75c | ||
|
|
f60a481902 | ||
|
|
e89c77a1bc | ||
|
|
45766b8c12 | ||
|
|
1c44fcb070 | ||
|
|
25aa815a82 | ||
|
|
a2542f1b18 | ||
|
|
2e0979dc5a | ||
|
|
e2ebf25b7b | ||
|
|
cc44b830a2 | ||
|
|
687cb6c7bc | ||
|
|
15f028157e | ||
|
|
63ebef56b0 | ||
|
|
258093e52a | ||
|
|
b09fdcd5f5 | ||
|
|
2277e95dd2 | ||
|
|
e0486bec7d | ||
|
|
29d44d103d | ||
|
|
44aa100566 | ||
|
|
23eb9cc44b | ||
|
|
f1797282d9 | ||
|
|
0c0740d488 | ||
|
|
a4faaa20c3 | ||
|
|
ce03b74d48 | ||
|
|
ce8ada05f5 | ||
|
|
b4dfe5beb6 | ||
|
|
22ecf0d06f | ||
|
|
1e66787422 |
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using mRemoteNG.Config.Connections;
|
||||
@@ -19,7 +19,7 @@ namespace mRemoteNG.App
|
||||
{
|
||||
public static class Export
|
||||
{
|
||||
public static void ExportToFile(ConnectionInfo selectedNode, ConnectionTreeModel connectionTreeModel)
|
||||
public static void ExportToFile(ConnectionInfo selectedNode, IConnectionTreeModel connectionTreeModel)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -93,8 +93,7 @@ namespace mRemoteNG.App
|
||||
serializer = new XmlConnectionsSerializer(cryptographyProvider, connectionNodeSerializer);
|
||||
break;
|
||||
case SaveFormat.mRCSV:
|
||||
serializer =
|
||||
new CsvConnectionsSerializerMremotengFormat(saveFilter, Runtime.CredentialProviderCatalog);
|
||||
serializer = new CsvConnectionsSerializerMremotengFormat(saveFilter, Runtime.CredentialService.RepositoryList);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(saveFormat), saveFormat, null);
|
||||
|
||||
@@ -10,7 +10,7 @@ using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.App
|
||||
{
|
||||
public static class Import
|
||||
public static class Import
|
||||
{
|
||||
public static void ImportFromFile(ContainerInfo importDestinationContainer)
|
||||
{
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using mRemoteNG.Config.Connections;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Properties;
|
||||
|
||||
namespace mRemoteNG.App.Initialization
|
||||
{
|
||||
public class CredsAndConsSetup
|
||||
{
|
||||
public void LoadCredsAndCons()
|
||||
public void LoadCredsAndCons(ConnectionsService connectionsService, CredentialService credentialService, SaveConnectionsOnEdit connectionsOnEdit)
|
||||
{
|
||||
new SaveConnectionsOnEdit(Runtime.ConnectionsService);
|
||||
connectionsOnEdit.Subscribe(connectionsService);
|
||||
|
||||
if (Settings.Default.FirstStart && !Settings.Default.LoadConsFromCustomLocation &&
|
||||
!File.Exists(Runtime.ConnectionsService.GetStartupConnectionFileName()))
|
||||
Runtime.ConnectionsService.NewConnectionsFile(Runtime.ConnectionsService
|
||||
.GetStartupConnectionFileName());
|
||||
if (Settings.Default.FirstStart && !Settings.Default.LoadConsFromCustomLocation &&
|
||||
!File.Exists(connectionsService.GetStartupConnectionFileName()))
|
||||
connectionsService.NewConnectionsFile(connectionsService.GetStartupConnectionFileName());
|
||||
|
||||
credentialService.LoadRepositoryList();
|
||||
LoadDefaultConnectionCredentials(credentialService);
|
||||
Runtime.LoadConnections();
|
||||
}
|
||||
|
||||
private void LoadDefaultConnectionCredentials(CredentialService credentialService)
|
||||
{
|
||||
var defaultCredId = Settings.Default.ConDefaultCredentialRecord;
|
||||
var matchedCredentials = credentialService
|
||||
.GetCredentialRecords()
|
||||
.Where(record => record.Id.Equals(defaultCredId))
|
||||
.ToArray();
|
||||
|
||||
DefaultConnectionInfo.Instance.CredentialRecordId = Optional<Guid>.FromNullable(matchedCredentials.FirstOrDefault()?.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using mRemoteNG.Config.Putty;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.Credential.Repositories;
|
||||
using mRemoteNG.Messages;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tools;
|
||||
@@ -36,7 +35,7 @@ namespace mRemoteNG.App
|
||||
/// <summary>
|
||||
/// Feature flag to enable the credential manager feature
|
||||
/// </summary>
|
||||
public static bool UseCredentialManager => false;
|
||||
public static bool UseCredentialManager => true;
|
||||
|
||||
public static WindowList WindowList { get; set; }
|
||||
public static MessageCollector MessageCollector { get; } = new MessageCollector();
|
||||
@@ -46,10 +45,9 @@ namespace mRemoteNG.App
|
||||
public static SecureString EncryptionKey { get; set; } =
|
||||
new RootNodeInfo(RootNodeType.Connection).PasswordString.ConvertToSecureString();
|
||||
|
||||
public static ICredentialRepositoryList CredentialProviderCatalog { get; } = new CredentialRepositoryList();
|
||||
public static CredentialService CredentialService { get; } = new CredentialServiceFactory().Build();
|
||||
|
||||
public static ConnectionsService ConnectionsService { get; } =
|
||||
new ConnectionsService(PuttySessionsManager.Instance);
|
||||
public static ConnectionsService ConnectionsService { get; } = new ConnectionsService(PuttySessionsManager.Instance, CredentialService);
|
||||
|
||||
#region Connections Loading/Saving
|
||||
|
||||
@@ -96,7 +94,7 @@ namespace mRemoteNG.App
|
||||
connectionFileName = ConnectionsService.GetStartupConnectionFileName();
|
||||
}
|
||||
|
||||
ConnectionsService.LoadConnections(Settings.Default.UseSQLServer, false, connectionFileName);
|
||||
ConnectionsService.LoadConnections(Settings.Default.UseSQLServer, connectionFileName);
|
||||
|
||||
if (Settings.Default.UseSQLServer)
|
||||
{
|
||||
|
||||
14
mRemoteNG/Config/ConnectionToCredentialMap.cs
Normal file
14
mRemoteNG/Config/ConnectionToCredentialMap.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using mRemoteNG.Credential;
|
||||
|
||||
namespace mRemoteNG.Config
|
||||
{
|
||||
public class ConnectionToCredentialMap : Dictionary<Guid, ICredentialRecord>
|
||||
{
|
||||
private readonly IEqualityComparer<ICredentialRecord> _credentialComparer = new CredentialDomainUserPasswordComparer();
|
||||
|
||||
public IEnumerable<ICredentialRecord> DistinctCredentialRecords => Values.Distinct(_credentialComparer);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tree;
|
||||
|
||||
@@ -10,7 +12,7 @@ namespace mRemoteNG.Config.Connections
|
||||
/// The previous <see cref="ConnectionTreeModel"/> that is being
|
||||
/// unloaded.
|
||||
/// </summary>
|
||||
public Optional<ConnectionTreeModel> PreviousConnectionTreeModel { get; }
|
||||
public List<ConnectionInfo> RemovedConnections { get; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the previous <see cref="ConnectionTreeModel"/> was loaded from
|
||||
@@ -21,7 +23,7 @@ namespace mRemoteNG.Config.Connections
|
||||
/// <summary>
|
||||
/// The new <see cref="ConnectionTreeModel"/> that is being loaded.
|
||||
/// </summary>
|
||||
public ConnectionTreeModel NewConnectionTreeModel { get; }
|
||||
public List<ConnectionInfo> AddedConnections { get; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the new <see cref="ConnectionTreeModel"/> was loaded from
|
||||
@@ -36,24 +38,18 @@ namespace mRemoteNG.Config.Connections
|
||||
/// </summary>
|
||||
public string NewSourcePath { get; }
|
||||
|
||||
public ConnectionsLoadedEventArgs(Optional<ConnectionTreeModel> previousTreeModelModel,
|
||||
ConnectionTreeModel newTreeModelModel,
|
||||
bool previousSourceWasDatabase,
|
||||
bool newSourceIsDatabase,
|
||||
string newSourcePath)
|
||||
{
|
||||
if (previousTreeModelModel == null)
|
||||
throw new ArgumentNullException(nameof(previousTreeModelModel));
|
||||
if (newTreeModelModel == null)
|
||||
throw new ArgumentNullException(nameof(newTreeModelModel));
|
||||
if (newSourcePath == null)
|
||||
throw new ArgumentNullException(nameof(newSourcePath));
|
||||
public IConnectionTreeModel NewConnectionTreeModel { get; set; } = new ConnectionTreeModel();
|
||||
|
||||
PreviousConnectionTreeModel = previousTreeModelModel;
|
||||
public ConnectionsLoadedEventArgs(
|
||||
List<ConnectionInfo> removedConnections, List<ConnectionInfo> addedConnections,
|
||||
bool previousSourceWasDatabase, bool newSourceIsDatabase,
|
||||
string newSourcePath)
|
||||
{
|
||||
RemovedConnections = removedConnections.ThrowIfNull(nameof(removedConnections));
|
||||
PreviousSourceWasDatabase = previousSourceWasDatabase;
|
||||
NewConnectionTreeModel = newTreeModelModel;
|
||||
AddedConnections = addedConnections.ThrowIfNull(nameof(addedConnections));
|
||||
NewSourceIsDatabase = newSourceIsDatabase;
|
||||
NewSourcePath = newSourcePath;
|
||||
NewSourcePath = newSourcePath.ThrowIfNull(nameof(newSourcePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ namespace mRemoteNG.Config.Connections
|
||||
{
|
||||
public class ConnectionsSavedEventArgs
|
||||
{
|
||||
public ConnectionTreeModel ModelThatWasSaved { get; }
|
||||
public IConnectionTreeModel ModelThatWasSaved { get; }
|
||||
public bool PreviouslyUsingDatabase { get; }
|
||||
public bool UsingDatabase { get; }
|
||||
public string ConnectionFileName { get; }
|
||||
|
||||
public ConnectionsSavedEventArgs(ConnectionTreeModel modelThatWasSaved,
|
||||
public ConnectionsSavedEventArgs(IConnectionTreeModel modelThatWasSaved,
|
||||
bool previouslyUsingDatabase,
|
||||
bool usingDatabase,
|
||||
string connectionFileName)
|
||||
@@ -24,4 +24,4 @@ namespace mRemoteNG.Config.Connections
|
||||
ConnectionFileName = connectionFileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,10 @@ namespace mRemoteNG.Config.Connections
|
||||
|
||||
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
|
||||
{
|
||||
var csvConnectionsSerializer =
|
||||
new CsvConnectionsSerializerMremotengFormat(_saveFilter, Runtime.CredentialProviderCatalog);
|
||||
var csvConnectionsSerializer = new CsvConnectionsSerializerMremotengFormat(_saveFilter, Runtime.CredentialService.RepositoryList);
|
||||
var dataProvider = new FileDataProvider(_connectionFileName);
|
||||
var csvContent = csvConnectionsSerializer.Serialize(connectionTreeModel);
|
||||
dataProvider.Save(csvContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Config.Serializers;
|
||||
|
||||
namespace mRemoteNG.Config.Connections
|
||||
{
|
||||
public interface IConnectionsLoader
|
||||
{
|
||||
ConnectionTreeModel Load();
|
||||
SerializationResult Load();
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace mRemoteNG.Config.Connections.Multiuser
|
||||
|
||||
private void Load(object sender, ConnectionsUpdateAvailableEventArgs args)
|
||||
{
|
||||
Runtime.ConnectionsService.LoadConnections(true, false, "");
|
||||
Runtime.ConnectionsService.LoadConnections(true, "");
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.UI.Forms;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.Config.Connections
|
||||
{
|
||||
public class SaveConnectionsOnEdit
|
||||
{
|
||||
private readonly ConnectionsService _connectionsService;
|
||||
private IConnectionsService _connectionsService;
|
||||
|
||||
public SaveConnectionsOnEdit(ConnectionsService connectionsService)
|
||||
public void Subscribe(IConnectionsService connectionsService)
|
||||
{
|
||||
if (connectionsService == null)
|
||||
throw new ArgumentNullException(nameof(connectionsService));
|
||||
|
||||
_connectionsService = connectionsService;
|
||||
connectionsService.ConnectionsLoaded += ConnectionsServiceOnConnectionsLoaded;
|
||||
_connectionsService = connectionsService.ThrowIfNull(nameof(connectionsService));
|
||||
connectionsService.ConnectionTreeModel.CollectionChanged += ConnectionTreeModelOnCollectionChanged;
|
||||
connectionsService.ConnectionTreeModel.PropertyChanged += ConnectionTreeModelOnPropertyChanged;
|
||||
}
|
||||
|
||||
private void ConnectionsServiceOnConnectionsLoaded(object sender,
|
||||
ConnectionsLoadedEventArgs connectionsLoadedEventArgs)
|
||||
public void Unsubscribe()
|
||||
{
|
||||
connectionsLoadedEventArgs.NewConnectionTreeModel.CollectionChanged +=
|
||||
ConnectionTreeModelOnCollectionChanged;
|
||||
connectionsLoadedEventArgs.NewConnectionTreeModel.PropertyChanged += ConnectionTreeModelOnPropertyChanged;
|
||||
|
||||
foreach (var oldTree in connectionsLoadedEventArgs.PreviousConnectionTreeModel)
|
||||
{
|
||||
oldTree.CollectionChanged -= ConnectionTreeModelOnCollectionChanged;
|
||||
oldTree.PropertyChanged -= ConnectionTreeModelOnPropertyChanged;
|
||||
}
|
||||
_connectionsService.ConnectionTreeModel.CollectionChanged -= ConnectionTreeModelOnCollectionChanged;
|
||||
_connectionsService.ConnectionTreeModel.PropertyChanged -= ConnectionTreeModelOnPropertyChanged;
|
||||
_connectionsService = null;
|
||||
}
|
||||
|
||||
private void ConnectionTreeModelOnPropertyChanged(object sender,
|
||||
@@ -50,10 +40,8 @@ namespace mRemoteNG.Config.Connections
|
||||
{
|
||||
if (!Properties.Settings.Default.SaveConnectionsAfterEveryEdit)
|
||||
return;
|
||||
if (FrmMain.Default.IsClosing)
|
||||
return;
|
||||
|
||||
_connectionsService.SaveConnectionsAsync(propertyName);
|
||||
_connectionsService?.SaveConnectionsAsync(propertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using mRemoteNG.Security;
|
||||
using mRemoteNG.Security.Authentication;
|
||||
using mRemoteNG.Security.SymmetricEncryption;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
|
||||
namespace mRemoteNG.Config.Connections
|
||||
@@ -36,7 +35,7 @@ namespace mRemoteNG.Config.Connections
|
||||
_dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
|
||||
}
|
||||
|
||||
public ConnectionTreeModel Load()
|
||||
public SerializationResult Load()
|
||||
{
|
||||
var connector = DatabaseConnectorFactory.DatabaseConnectorFromSettings();
|
||||
var dataProvider = new SqlDataProvider(connector);
|
||||
@@ -54,9 +53,9 @@ namespace mRemoteNG.Config.Connections
|
||||
databaseVersionVerifier.VerifyDatabaseVersion(metaData.ConfVersion);
|
||||
var dataTable = dataProvider.Load();
|
||||
var deserializer = new DataTableDeserializer(cryptoProvider, decryptionKey.First());
|
||||
var connectionTree = deserializer.Deserialize(dataTable);
|
||||
ApplyLocalConnectionProperties(connectionTree.RootNodes.First(i => i is RootNodeInfo));
|
||||
return connectionTree;
|
||||
var serializationResult = deserializer.Deserialize(dataTable);
|
||||
ApplyLocalConnectionProperties(serializationResult.ConnectionRecords.OfType<RootNodeInfo>().First());
|
||||
return serializationResult;
|
||||
}
|
||||
|
||||
private Optional<SecureString> GetDecryptionKey(SqlConnectionListMetaData metaData)
|
||||
|
||||
@@ -20,7 +20,7 @@ using mRemoteNG.Tree.Root;
|
||||
|
||||
namespace mRemoteNG.Config.Connections
|
||||
{
|
||||
public class SqlConnectionsSaver : ISaver<ConnectionTreeModel>
|
||||
public class SqlConnectionsSaver : ISaver<IConnectionTreeModel>
|
||||
{
|
||||
private readonly SaveFilter _saveFilter;
|
||||
private readonly ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> _localPropertiesSerializer;
|
||||
@@ -38,7 +38,7 @@ namespace mRemoteNG.Config.Connections
|
||||
_dataProvider = localPropertiesDataProvider.ThrowIfNull(nameof(localPropertiesDataProvider));
|
||||
}
|
||||
|
||||
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
|
||||
public void Save(IConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
|
||||
{
|
||||
var rootTreeNode = connectionTreeModel.RootNodes.OfType<RootNodeInfo>().First();
|
||||
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tree;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
|
||||
using mRemoteNG.App.Info;
|
||||
using mRemoteNG.Config.Serializers;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.UI.Forms;
|
||||
|
||||
namespace mRemoteNG.Config.Connections
|
||||
{
|
||||
public class XmlConnectionsLoader : IConnectionsLoader
|
||||
{
|
||||
private readonly string _credentialFilePath = Path.Combine(CredentialsFileInfo.CredentialsPath, CredentialsFileInfo.CredentialsFile);
|
||||
private readonly string _connectionFilePath;
|
||||
private readonly ConnectionsService _connectionsService;
|
||||
private readonly ICredentialService _credentialService;
|
||||
|
||||
public XmlConnectionsLoader(string connectionFilePath)
|
||||
public XmlConnectionsLoader(string connectionFilePath, ICredentialService credentialService, ConnectionsService connectionsService)
|
||||
{
|
||||
if (string.IsNullOrEmpty(connectionFilePath))
|
||||
throw new ArgumentException($"{nameof(connectionFilePath)} cannot be null or empty");
|
||||
@@ -21,14 +28,26 @@ namespace mRemoteNG.Config.Connections
|
||||
throw new FileNotFoundException($"{connectionFilePath} does not exist");
|
||||
|
||||
_connectionFilePath = connectionFilePath;
|
||||
_connectionsService = connectionsService.ThrowIfNull(nameof(connectionsService));
|
||||
_credentialService = credentialService.ThrowIfNull(nameof(credentialService));
|
||||
}
|
||||
|
||||
public ConnectionTreeModel Load()
|
||||
public SerializationResult Load()
|
||||
{
|
||||
var dataProvider = new FileDataProvider(_connectionFilePath);
|
||||
var xmlString = dataProvider.Load();
|
||||
var deserializer = new XmlConnectionsDeserializer(PromptForPassword);
|
||||
return deserializer.Deserialize(xmlString);
|
||||
var deserializer = new CredentialManagerUpgradeForm
|
||||
{
|
||||
ConnectionFilePath = _connectionFilePath,
|
||||
NewCredentialRepoPath = _credentialFilePath,
|
||||
ConnectionsService = _connectionsService,
|
||||
CredentialService = _credentialService,
|
||||
ConnectionDeserializer = new XmlConnectionsDeserializer(PromptForPassword)
|
||||
};
|
||||
|
||||
var serializationResult = deserializer.Deserialize(xmlString);
|
||||
|
||||
return serializationResult;
|
||||
}
|
||||
|
||||
private Optional<SecureString> PromptForPassword()
|
||||
|
||||
@@ -10,7 +10,7 @@ using mRemoteNG.Tree.Root;
|
||||
|
||||
namespace mRemoteNG.Config.Connections
|
||||
{
|
||||
public class XmlConnectionsSaver : ISaver<ConnectionTreeModel>
|
||||
public class XmlConnectionsSaver : ISaver<IConnectionTreeModel>
|
||||
{
|
||||
private readonly string _connectionFileName;
|
||||
private readonly SaveFilter _saveFilter;
|
||||
@@ -26,7 +26,7 @@ namespace mRemoteNG.Config.Connections
|
||||
_saveFilter = saveFilter;
|
||||
}
|
||||
|
||||
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
|
||||
public void Save(IConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
39
mRemoteNG/Config/CredentialRepositoryListPersistor.cs
Normal file
39
mRemoteNG/Config/CredentialRepositoryListPersistor.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.Serializers.CredentialProviderSerializer;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.Credential.Repositories;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.Config
|
||||
{
|
||||
public class CredentialRepositoryListPersistor : ISaver<IEnumerable<ICredentialRepository>>, ILoader<IEnumerable<ICredentialRepository>>
|
||||
{
|
||||
private readonly IReadOnlyCollection<ICredentialRepositoryFactory> _repositoryFactories;
|
||||
private readonly IDataProvider<string> _dataProvider;
|
||||
private readonly CredentialRepositoryListDeserializer _deserializer;
|
||||
private readonly CredentialRepositoryListSerializer _serializer;
|
||||
|
||||
public CredentialRepositoryListPersistor(
|
||||
IDataProvider<string> dataProvider,
|
||||
IReadOnlyCollection<ICredentialRepositoryFactory> repositoryFactories)
|
||||
{
|
||||
_repositoryFactories = repositoryFactories.ThrowIfNull(nameof(repositoryFactories));
|
||||
_dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
|
||||
_deserializer = new CredentialRepositoryListDeserializer();
|
||||
_serializer = new CredentialRepositoryListSerializer();
|
||||
}
|
||||
|
||||
public IEnumerable<ICredentialRepository> Load()
|
||||
{
|
||||
var data = _dataProvider.Load();
|
||||
return _deserializer.Deserialize(data, _repositoryFactories);
|
||||
}
|
||||
|
||||
public void Save(IEnumerable<ICredentialRepository> repositories, string propertyNameTrigger = "")
|
||||
{
|
||||
var data = _serializer.Serialize(repositories);
|
||||
_dataProvider.Save(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.Serializers.ConnectionSerializers.Csv;
|
||||
using mRemoteNG.Container;
|
||||
using mRemoteNG.Messages;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using mRemoteNG.UI.Forms;
|
||||
|
||||
namespace mRemoteNG.Config.Import
|
||||
{
|
||||
@@ -25,11 +26,18 @@ namespace mRemoteNG.Config.Import
|
||||
var dataProvider = new FileDataProvider(filePath);
|
||||
var xmlString = dataProvider.Load();
|
||||
var xmlConnectionsDeserializer = new CsvConnectionsDeserializerMremotengFormat();
|
||||
var connectionTreeModel = xmlConnectionsDeserializer.Deserialize(xmlString);
|
||||
var serializationResult = xmlConnectionsDeserializer.Deserialize(xmlString);
|
||||
|
||||
var credentialImportForm = new CredentialImportForm
|
||||
{
|
||||
ImportedCredentialRecords = serializationResult.ConnectionToCredentialMap.DistinctCredentialRecords.ToList(),
|
||||
CredentialService = Runtime.CredentialService
|
||||
};
|
||||
credentialImportForm.ShowDialog();
|
||||
|
||||
var rootImportContainer = new ContainerInfo {Name = Path.GetFileNameWithoutExtension(filePath)};
|
||||
rootImportContainer.AddChildRange(connectionTreeModel.RootNodes.First().Children.ToArray());
|
||||
rootImportContainer.AddChildRange(serializationResult.ConnectionRecords);
|
||||
destinationContainer.AddChild(rootImportContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
|
||||
@@ -27,10 +26,10 @@ namespace mRemoteNG.Config.Import
|
||||
var dataProvider = new FileDataProvider(fileName);
|
||||
var xmlString = dataProvider.Load();
|
||||
var xmlConnectionsDeserializer = new XmlConnectionsDeserializer();
|
||||
var connectionTreeModel = xmlConnectionsDeserializer.Deserialize(xmlString, true);
|
||||
var serializationResult = xmlConnectionsDeserializer.Deserialize(xmlString, true);
|
||||
|
||||
var rootImportContainer = new ContainerInfo {Name = Path.GetFileNameWithoutExtension(fileName)};
|
||||
rootImportContainer.AddChildRange(connectionTreeModel.RootNodes.First().Children.ToArray());
|
||||
rootImportContainer.AddChildRange(serializationResult.ConnectionRecords);
|
||||
destinationContainer.AddChild(rootImportContainer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.Serializers.MiscSerializers;
|
||||
using mRemoteNG.Container;
|
||||
|
||||
@@ -14,12 +13,9 @@ namespace mRemoteNG.Config.Import
|
||||
var xmlContent = dataProvider.Load();
|
||||
|
||||
var deserializer = new PuttyConnectionManagerDeserializer();
|
||||
var connectionTreeModel = deserializer.Deserialize(xmlContent);
|
||||
var serializationResult = deserializer.Deserialize(xmlContent);
|
||||
|
||||
var importedRootNode = connectionTreeModel.RootNodes.First();
|
||||
if (importedRootNode == null) return;
|
||||
var childrenToAdd = importedRootNode.Children.ToArray();
|
||||
destinationContainer.AddChildRange(childrenToAdd);
|
||||
destinationContainer.AddChildRange(serializationResult.ConnectionRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,9 @@ namespace mRemoteNG.Config.Import
|
||||
var content = dataProvider.Load();
|
||||
|
||||
var deserializer = new RemoteDesktopConnectionDeserializer();
|
||||
var connectionTreeModel = deserializer.Deserialize(content);
|
||||
var serializationResult = deserializer.Deserialize(content);
|
||||
|
||||
var importedConnection = connectionTreeModel.RootNodes.First().Children.First();
|
||||
var importedConnection = serializationResult.ConnectionRecords.FirstOrDefault();
|
||||
|
||||
if (importedConnection == null) return;
|
||||
importedConnection.Name = Path.GetFileNameWithoutExtension(fileName);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.Serializers.MiscSerializers;
|
||||
using mRemoteNG.Container;
|
||||
|
||||
@@ -14,12 +13,9 @@ namespace mRemoteNG.Config.Import
|
||||
var fileContent = dataProvider.Load();
|
||||
|
||||
var deserializer = new RemoteDesktopConnectionManagerDeserializer();
|
||||
var connectionTreeModel = deserializer.Deserialize(fileContent);
|
||||
var serializationResult = deserializer.Deserialize(fileContent);
|
||||
|
||||
var importedRootNode = connectionTreeModel.RootNodes.First();
|
||||
if (importedRootNode == null) return;
|
||||
var childrenToAdd = importedRootNode.Children.ToArray();
|
||||
destinationContainer.AddChildRange(childrenToAdd);
|
||||
destinationContainer.AddChildRange(serializationResult.ConnectionRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management;
|
||||
using System.Net;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.Win32;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Messages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management;
|
||||
using System.Net;
|
||||
using System.Security.Principal;
|
||||
|
||||
|
||||
namespace mRemoteNG.Config.Putty
|
||||
@@ -55,6 +55,7 @@ namespace mRemoteNG.Config.Putty
|
||||
PuttySession = sessionName,
|
||||
Name = sessionName,
|
||||
Hostname = sessionKey.GetValue("HostName")?.ToString() ?? "",
|
||||
// TODO: this should create a temp putty credential
|
||||
Username = sessionKey.GetValue("UserName")?.ToString() ?? ""
|
||||
};
|
||||
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using mRemoteNG.Config.Serializers.CredentialSerializer;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Connection.Protocol.Http;
|
||||
using mRemoteNG.Connection.Protocol.RDP;
|
||||
using mRemoteNG.Connection.Protocol.VNC;
|
||||
using mRemoteNG.Container;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
{
|
||||
public class CsvConnectionsDeserializerMremotengFormat : IDeserializer<string, ConnectionTreeModel>
|
||||
public class CsvConnectionsDeserializerMremotengFormat
|
||||
{
|
||||
public ConnectionTreeModel Deserialize(string serializedData)
|
||||
public SerializationResult Deserialize(string serializedData)
|
||||
{
|
||||
var lines = serializedData.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries);
|
||||
var csvHeaders = new List<string>();
|
||||
@@ -29,26 +32,39 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
else
|
||||
{
|
||||
var connectionInfo = ParseConnectionInfo(csvHeaders, line);
|
||||
parentMapping.Add(connectionInfo, line[csvHeaders.IndexOf("Parent")]);
|
||||
|
||||
var parentFieldIndex = csvHeaders.IndexOf("Parent");
|
||||
if (parentFieldIndex > 0)
|
||||
parentMapping.Add(connectionInfo, line[parentFieldIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
var root = CreateTreeStructure(parentMapping);
|
||||
var connectionTreeModel = new ConnectionTreeModel();
|
||||
connectionTreeModel.AddRootNode(root);
|
||||
return connectionTreeModel;
|
||||
var harvestedCredentials = new CredentialHarvester()
|
||||
.Harvest(new HarvestConfig<string[]>
|
||||
{
|
||||
ItemEnumerator = () => lines.Skip(1).Select(s => s.Split(';')),
|
||||
ConnectionGuidSelector = line => csvHeaders.Contains("Id") ? Guid.Parse(line[csvHeaders.IndexOf("Id")]) : Guid.NewGuid(),
|
||||
TitleSelector = line => "",
|
||||
UsernameSelector = line => csvHeaders.Contains("Username") ? line[csvHeaders.IndexOf("Username")] : "",
|
||||
DomainSelector = line => csvHeaders.Contains("Domain") ? line[csvHeaders.IndexOf("Domain")] : "",
|
||||
PasswordSelector = line => csvHeaders.Contains("Password") ? line[csvHeaders.IndexOf("Password")].ConvertToSecureString() : new SecureString()
|
||||
});
|
||||
|
||||
var result = new SerializationResult(root, harvestedCredentials);
|
||||
return result;
|
||||
}
|
||||
|
||||
private RootNodeInfo CreateTreeStructure(Dictionary<ConnectionInfo, string> parentMapping)
|
||||
private List<ConnectionInfo> CreateTreeStructure(Dictionary<ConnectionInfo, string> parentMapping)
|
||||
{
|
||||
var root = new RootNodeInfo(RootNodeType.Connection);
|
||||
var root = new List<ConnectionInfo>();
|
||||
|
||||
foreach (var node in parentMapping)
|
||||
{
|
||||
// no parent mapped, add to root
|
||||
if (string.IsNullOrEmpty(node.Value))
|
||||
{
|
||||
root.AddChild(node.Key);
|
||||
root.Add(node.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -64,7 +80,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
}
|
||||
else
|
||||
{
|
||||
root.AddChild(node.Key);
|
||||
root.Add(node.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,53 +101,25 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
? new ConnectionInfo(nodeId)
|
||||
: new ContainerInfo(nodeId);
|
||||
|
||||
connectionRecord.Name = headers.Contains("Name")
|
||||
? connectionCsv[headers.IndexOf("Name")]
|
||||
: "";
|
||||
connectionRecord.Name = headers.Contains("Name") ? connectionCsv[headers.IndexOf("Name")] : "";
|
||||
connectionRecord.Description =
|
||||
headers.Contains("Description") ? connectionCsv[headers.IndexOf("Description")] : "";
|
||||
connectionRecord.Icon = headers.Contains("Icon") ? connectionCsv[headers.IndexOf("Icon")] : "";
|
||||
connectionRecord.Panel = headers.Contains("Panel") ? connectionCsv[headers.IndexOf("Panel")] : "";
|
||||
|
||||
connectionRecord.Description = headers.Contains("Description")
|
||||
? connectionCsv[headers.IndexOf("Description")]
|
||||
: "";
|
||||
var hasCredRecordId = Guid.TryParse(
|
||||
headers.Contains("CredentialRecordId") ? connectionCsv[headers.IndexOf("CredentialRecordId")] : "",
|
||||
out var credRecordId);
|
||||
connectionRecord.CredentialRecordId = hasCredRecordId ? credRecordId : Optional<Guid>.Empty;
|
||||
|
||||
connectionRecord.Icon = headers.Contains("Icon")
|
||||
? connectionCsv[headers.IndexOf("Icon")]
|
||||
: "";
|
||||
|
||||
connectionRecord.Panel = headers.Contains("Panel")
|
||||
? connectionCsv[headers.IndexOf("Panel")]
|
||||
: "";
|
||||
|
||||
connectionRecord.Username = headers.Contains("Username")
|
||||
? connectionCsv[headers.IndexOf("Username")]
|
||||
: "";
|
||||
|
||||
connectionRecord.Password = headers.Contains("Password")
|
||||
? connectionCsv[headers.IndexOf("Password")]
|
||||
: "";
|
||||
|
||||
connectionRecord.Domain = headers.Contains("Domain")
|
||||
? connectionCsv[headers.IndexOf("Domain")]
|
||||
: "";
|
||||
|
||||
connectionRecord.Hostname = headers.Contains("Hostname")
|
||||
? connectionCsv[headers.IndexOf("Hostname")]
|
||||
: "";
|
||||
|
||||
connectionRecord.VmId = headers.Contains("VmId")
|
||||
? connectionCsv[headers.IndexOf("VmId")] : "";
|
||||
|
||||
connectionRecord.SSHOptions =headers.Contains("SSHOptions")
|
||||
? connectionCsv[headers.IndexOf("SSHOptions")]
|
||||
: "";
|
||||
|
||||
connectionRecord.SSHTunnelConnectionName = headers.Contains("SSHTunnelConnectionName")
|
||||
? connectionCsv[headers.IndexOf("SSHTunnelConnectionName")]
|
||||
: "";
|
||||
|
||||
connectionRecord.PuttySession = headers.Contains("PuttySession")
|
||||
? connectionCsv[headers.IndexOf("PuttySession")]
|
||||
: "";
|
||||
// TODO: harvest
|
||||
connectionRecord.Username = headers.Contains("Username") ? connectionCsv[headers.IndexOf("Username")] : "";
|
||||
connectionRecord.Password = headers.Contains("Password") ? connectionCsv[headers.IndexOf("Password")] : "";
|
||||
connectionRecord.Domain = headers.Contains("Domain") ? connectionCsv[headers.IndexOf("Domain")] : "";
|
||||
|
||||
connectionRecord.Hostname = headers.Contains("Hostname") ? connectionCsv[headers.IndexOf("Hostname")] : "";
|
||||
connectionRecord.PuttySession =
|
||||
headers.Contains("PuttySession") ? connectionCsv[headers.IndexOf("PuttySession")] : "";
|
||||
connectionRecord.LoadBalanceInfo = headers.Contains("LoadBalanceInfo")
|
||||
? connectionCsv[headers.IndexOf("LoadBalanceInfo")]
|
||||
: "";
|
||||
@@ -186,6 +174,12 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
? connectionCsv[headers.IndexOf("RDGatewayHostname")]
|
||||
: "";
|
||||
|
||||
if (headers.Contains("CredentialId"))
|
||||
{
|
||||
if (Guid.TryParse(connectionCsv[headers.IndexOf("CredentialId")], out var credId))
|
||||
connectionRecord.CredentialRecordId = credId;
|
||||
}
|
||||
|
||||
if (headers.Contains("Protocol"))
|
||||
{
|
||||
if (Enum.TryParse(connectionCsv[headers.IndexOf("Protocol")], out ProtocolType protocolType))
|
||||
@@ -825,9 +819,16 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
connectionRecord.Inheritance.RdpVersion = value;
|
||||
}
|
||||
|
||||
|
||||
if (headers.Contains("InheritCredentialRecord"))
|
||||
{
|
||||
if (bool.TryParse(connectionCsv[headers.IndexOf("InheritCredentialRecord")], out bool value))
|
||||
connectionRecord.Inheritance.CredentialId = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return connectionRecord;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
"CacheBitmaps;RedirectDiskDrives;RedirectPorts;RedirectPrinters;RedirectClipboard;RedirectSmartCards;RedirectSound;RedirectKeys;" +
|
||||
"PreExtApp;PostExtApp;MacAddress;UserField;ExtApp;Favorite;VNCCompression;VNCEncoding;VNCAuthMode;VNCProxyType;VNCProxyIP;" +
|
||||
"VNCProxyPort;VNCProxyUsername;VNCProxyPassword;VNCColors;VNCSmartSizeMode;VNCViewOnly;RDGatewayUsageMethod;RDGatewayHostname;" +
|
||||
"RDGatewayUseConnectionCredentials;RDGatewayUsername;RDGatewayPassword;RDGatewayDomain;RedirectAudioCapture;RdpVersion;");
|
||||
"RDGatewayUseConnectionCredentials;RDGatewayUsername;RDGatewayPassword;RDGatewayDomain;RedirectAudioCapture;RdpVersion;CredentialId");
|
||||
|
||||
if (_saveFilter.SaveInheritance)
|
||||
sb.Append("InheritCacheBitmaps;InheritColors;InheritDescription;InheritDisplayThemes;InheritDisplayWallpaper;" +
|
||||
@@ -74,7 +74,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
"InheritVNCProxyPort;InheritVNCProxyUsername;InheritVNCProxyPassword;InheritVNCColors;InheritVNCSmartSizeMode;InheritVNCViewOnly;" +
|
||||
"InheritRDGatewayUsageMethod;InheritRDGatewayHostname;InheritRDGatewayUseConnectionCredentials;InheritRDGatewayUsername;" +
|
||||
"InheritRDGatewayPassword;InheritRDGatewayDomain;InheritRDPAlertIdleTimeout;InheritRDPMinutesToIdleTimeout;InheritSoundQuality;" +
|
||||
"InheritRedirectAudioCapture;InheritRdpVersion");
|
||||
"InheritRedirectAudioCapture;InheritRdpVersion;InheritCredentialRecord");
|
||||
}
|
||||
|
||||
private void SerializeNodesRecursive(ConnectionInfo node, StringBuilder sb)
|
||||
@@ -104,18 +104,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
.Append(FormatForCsv(con.GetTreeNodeType()))
|
||||
.Append(FormatForCsv(con.Description))
|
||||
.Append(FormatForCsv(con.Icon))
|
||||
.Append(FormatForCsv(con.Panel));
|
||||
|
||||
if (_saveFilter.SaveUsername)
|
||||
sb.Append(FormatForCsv(con.Username));
|
||||
|
||||
if (_saveFilter.SavePassword)
|
||||
sb.Append(FormatForCsv(con.Password));
|
||||
|
||||
if (_saveFilter.SaveDomain)
|
||||
sb.Append(FormatForCsv(con.Domain));
|
||||
|
||||
sb.Append(FormatForCsv(con.Hostname))
|
||||
.Append(FormatForCsv(con.Panel))
|
||||
.Append(FormatForCsv(con.Hostname))
|
||||
.Append(FormatForCsv(con.Port))
|
||||
.Append(FormatForCsv(con.VmId))
|
||||
.Append(FormatForCsv(con.Protocol))
|
||||
@@ -172,7 +162,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
.Append(FormatForCsv(con.RDGatewayPassword))
|
||||
.Append(FormatForCsv(con.RDGatewayDomain))
|
||||
.Append(FormatForCsv(con.RedirectAudioCapture))
|
||||
.Append(FormatForCsv(con.RdpVersion));
|
||||
.Append(FormatForCsv(con.RdpVersion))
|
||||
.Append(FormatForCsv(con.CredentialRecordId));
|
||||
|
||||
|
||||
if (!_saveFilter.SaveInheritance)
|
||||
@@ -243,7 +234,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Csv
|
||||
.Append(FormatForCsv(con.Inheritance.RDPMinutesToIdleTimeout))
|
||||
.Append(FormatForCsv(con.Inheritance.SoundQuality))
|
||||
.Append(FormatForCsv(con.Inheritance.RedirectAudioCapture))
|
||||
.Append(FormatForCsv(con.Inheritance.RdpVersion));
|
||||
.Append(FormatForCsv(con.Inheritance.RdpVersion))
|
||||
.Append(FormatForCsv(con.Inheritance.CredentialId));
|
||||
}
|
||||
|
||||
private string FormatForCsv(object value)
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Connection.Protocol.Http;
|
||||
@@ -12,12 +11,10 @@ using mRemoteNG.Connection.Protocol.VNC;
|
||||
using mRemoteNG.Container;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
{
|
||||
public class DataTableDeserializer : IDeserializer<DataTable, ConnectionTreeModel>
|
||||
public class DataTableDeserializer
|
||||
{
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
private readonly SecureString _decryptionKey;
|
||||
@@ -28,12 +25,13 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
_decryptionKey = decryptionKey.ThrowIfNull(nameof(decryptionKey));
|
||||
}
|
||||
|
||||
public ConnectionTreeModel Deserialize(DataTable table)
|
||||
public SerializationResult Deserialize(DataTable table)
|
||||
{
|
||||
var connectionList = CreateNodesFromTable(table);
|
||||
var connectionTreeModel = CreateNodeHierarchy(connectionList, table);
|
||||
Runtime.ConnectionsService.IsConnectionsFileLoaded = true;
|
||||
return connectionTreeModel;
|
||||
var rootNodes = CreateNodeHierarchy(connectionList, table);
|
||||
|
||||
var serializationResult = new SerializationResult(rootNodes, new ConnectionToCredentialMap());
|
||||
return serializationResult;
|
||||
}
|
||||
|
||||
private List<ConnectionInfo> CreateNodesFromTable(DataTable table)
|
||||
@@ -60,7 +58,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
{
|
||||
var connectionId = row["ConstantID"] as string ?? Guid.NewGuid().ToString();
|
||||
var connectionInfo = new ConnectionInfo(connectionId);
|
||||
PopulateConnectionInfoFromDatarow(row, connectionInfo);
|
||||
PopulateConnectionInfoFromDataRow(row, connectionInfo);
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
@@ -68,11 +66,11 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
{
|
||||
var containerId = row["ConstantID"] as string ?? Guid.NewGuid().ToString();
|
||||
var containerInfo = new ContainerInfo(containerId);
|
||||
PopulateConnectionInfoFromDatarow(row, containerInfo);
|
||||
PopulateConnectionInfoFromDataRow(row, containerInfo);
|
||||
return containerInfo;
|
||||
}
|
||||
|
||||
private void PopulateConnectionInfoFromDatarow(DataRow dataRow, ConnectionInfo connectionInfo)
|
||||
private void PopulateConnectionInfoFromDataRow(DataRow dataRow, ConnectionInfo connectionInfo)
|
||||
{
|
||||
connectionInfo.Name = (string)dataRow["Name"];
|
||||
|
||||
@@ -83,9 +81,15 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
connectionInfo.Description = (string)dataRow["Description"];
|
||||
connectionInfo.Icon = (string)dataRow["Icon"];
|
||||
connectionInfo.Panel = (string)dataRow["Panel"];
|
||||
connectionInfo.Username = (string)dataRow["Username"];
|
||||
connectionInfo.Domain = (string)dataRow["Domain"];
|
||||
connectionInfo.Password = DecryptValue((string)dataRow["Password"]);
|
||||
|
||||
// TODO: harvest
|
||||
if (dataRow.Table.Columns.Contains("Username"))
|
||||
connectionInfo.Username = (string)dataRow["Username"];
|
||||
if (dataRow.Table.Columns.Contains("DomainName"))
|
||||
connectionInfo.Domain = (string)dataRow["DomainName"];
|
||||
if (dataRow.Table.Columns.Contains("Password"))
|
||||
connectionInfo.Password = DecryptValue((string)dataRow["Password"]);
|
||||
|
||||
connectionInfo.Hostname = (string)dataRow["Hostname"];
|
||||
connectionInfo.VmId = (string)dataRow["VmId"];
|
||||
connectionInfo.UseEnhancedMode = (bool)dataRow["UseEnhancedMode"];
|
||||
@@ -184,7 +188,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
connectionInfo.Inheritance.Domain = (bool)dataRow["InheritDomain"];
|
||||
connectionInfo.Inheritance.Icon = (bool)dataRow["InheritIcon"];
|
||||
connectionInfo.Inheritance.Panel = (bool)dataRow["InheritPanel"];
|
||||
connectionInfo.Inheritance.Password = (bool)dataRow["InheritPassword"];
|
||||
connectionInfo.Inheritance.Port = (bool)dataRow["InheritPort"];
|
||||
connectionInfo.Inheritance.Protocol = (bool)dataRow["InheritProtocol"];
|
||||
connectionInfo.Inheritance.SSHTunnelConnectionName = (bool)dataRow["InheritSSHTunnelConnectionName"];
|
||||
@@ -251,28 +254,21 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
}
|
||||
}
|
||||
|
||||
private ConnectionTreeModel CreateNodeHierarchy(List<ConnectionInfo> connectionList, DataTable dataTable)
|
||||
private List<ConnectionInfo> CreateNodeHierarchy(IReadOnlyCollection<ConnectionInfo> connectionList, DataTable dataTable)
|
||||
{
|
||||
var connectionTreeModel = new ConnectionTreeModel();
|
||||
var rootNode = new RootNodeInfo(RootNodeType.Connection, "0")
|
||||
{
|
||||
PasswordString = _decryptionKey.ConvertToUnsecureString()
|
||||
};
|
||||
connectionTreeModel.AddRootNode(rootNode);
|
||||
var rootNodes = new List<ConnectionInfo>();
|
||||
|
||||
foreach (DataRow row in dataTable.Rows)
|
||||
{
|
||||
var id = (string)row["ConstantID"];
|
||||
var id = (string) row["ConstantID"];
|
||||
var connectionInfo = connectionList.First(node => node.ConstantID == id);
|
||||
var parentId = (string)row["ParentID"];
|
||||
var parentId = (string) row["ParentID"];
|
||||
if (parentId == "0" || connectionList.All(node => node.ConstantID != parentId))
|
||||
rootNode.AddChild(connectionInfo);
|
||||
rootNodes.Add(connectionInfo);
|
||||
else
|
||||
(connectionList.First(node => node.ConstantID == parentId) as ContainerInfo)?.AddChild(
|
||||
connectionInfo);
|
||||
(connectionList.First(node => node.ConstantID == parentId) as ContainerInfo)?.AddChild(connectionInfo);
|
||||
}
|
||||
|
||||
return connectionTreeModel;
|
||||
return rootNodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,9 +105,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
dataTable.Columns.Add("Description", typeof(string));
|
||||
dataTable.Columns.Add("Icon", typeof(string));
|
||||
dataTable.Columns.Add("Panel", typeof(string));
|
||||
dataTable.Columns.Add("Username", typeof(string));
|
||||
dataTable.Columns.Add("Domain", typeof(string));
|
||||
dataTable.Columns.Add("Password", typeof(string));
|
||||
dataTable.Columns.Add("Hostname", typeof(string));
|
||||
dataTable.Columns.Add("Port", typeof(int));
|
||||
dataTable.Columns.Add("Protocol", typeof(string));
|
||||
@@ -502,16 +499,10 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
dataRow["ParentID"] = connectionInfo.Parent?.ConstantID ?? "";
|
||||
dataRow["PositionID"] = _currentNodeIndex;
|
||||
dataRow["LastChange"] = MiscTools.DBTimeStampNow();
|
||||
dataRow["Expanded"] =
|
||||
false; // TODO: this column can eventually be removed. we now save this property locally
|
||||
dataRow["Description"] = connectionInfo.Description;
|
||||
dataRow["Icon"] = connectionInfo.Icon;
|
||||
dataRow["Panel"] = connectionInfo.Panel;
|
||||
dataRow["Username"] = _saveFilter.SaveUsername ? connectionInfo.Username : "";
|
||||
dataRow["Domain"] = _saveFilter.SaveDomain ? connectionInfo.Domain : "";
|
||||
dataRow["Password"] = _saveFilter.SavePassword
|
||||
? _cryptographyProvider.Encrypt(connectionInfo.Password, _encryptionKey)
|
||||
: "";
|
||||
|
||||
dataRow["Hostname"] = connectionInfo.Hostname;
|
||||
dataRow["VmId"] = connectionInfo.VmId;
|
||||
dataRow["Protocol"] = connectionInfo.Protocol;
|
||||
@@ -549,7 +540,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
dataRow["SoundQuality"] = connectionInfo.SoundQuality;
|
||||
dataRow["RedirectAudioCapture"] = connectionInfo.RedirectAudioCapture;
|
||||
dataRow["RedirectKeys"] = connectionInfo.RedirectKeys;
|
||||
dataRow["Connected"] = false; // TODO: this column can eventually be removed. we now save this property locally
|
||||
dataRow["PreExtApp"] = connectionInfo.PreExtApp;
|
||||
dataRow["PostExtApp"] = connectionInfo.PostExtApp;
|
||||
dataRow["MacAddress"] = connectionInfo.MacAddress;
|
||||
@@ -592,7 +582,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
dataRow["InheritDomain"] = connectionInfo.Inheritance.Domain;
|
||||
dataRow["InheritIcon"] = connectionInfo.Inheritance.Icon;
|
||||
dataRow["InheritPanel"] = connectionInfo.Inheritance.Panel;
|
||||
dataRow["InheritPassword"] = connectionInfo.Inheritance.Password;
|
||||
dataRow["InheritPort"] = connectionInfo.Inheritance.Port;
|
||||
dataRow["InheritProtocol"] = connectionInfo.Inheritance.Protocol;
|
||||
dataRow["InheritSSHTunnelConnectionName"] = connectionInfo.Inheritance.SSHTunnelConnectionName;
|
||||
@@ -660,7 +649,6 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql
|
||||
dataRow["InheritDomain"] = false;
|
||||
dataRow["InheritIcon"] = false;
|
||||
dataRow["InheritPanel"] = false;
|
||||
dataRow["InheritPassword"] = false;
|
||||
dataRow["InheritPort"] = false;
|
||||
dataRow["InheritProtocol"] = false;
|
||||
dataRow["InheritSSHTunnelConnectionName"] = false;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
{
|
||||
public ISerializer<ConnectionInfo, string> Build(
|
||||
ICryptographyProvider cryptographyProvider,
|
||||
ConnectionTreeModel connectionTreeModel,
|
||||
IConnectionTreeModel connectionTreeModel,
|
||||
SaveFilter saveFilter = null,
|
||||
bool useFullEncryption = false)
|
||||
{
|
||||
|
||||
@@ -17,17 +17,19 @@ using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
using mRemoteNG.UI.Forms;
|
||||
using mRemoteNG.UI.TaskDialog;
|
||||
using System.Collections.Generic;
|
||||
using mRemoteNG.Credential;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
{
|
||||
public class XmlConnectionsDeserializer : IDeserializer<string, ConnectionTreeModel>
|
||||
public class XmlConnectionsDeserializer
|
||||
{
|
||||
private XmlDocument _xmlDocument;
|
||||
private double _confVersion;
|
||||
private XmlConnectionsDecryptor _decryptor;
|
||||
private string ConnectionFileName = "";
|
||||
private const double MaxSupportedConfVersion = 2.8;
|
||||
private readonly RootNodeInfo _rootNodeInfo = new RootNodeInfo(RootNodeType.Connection);
|
||||
private const double MaxSupportedConfVersion = 2.7;
|
||||
private readonly CredentialDomainUserPasswordComparer _credentialComparer = new CredentialDomainUserPasswordComparer();
|
||||
|
||||
public Func<Optional<SecureString>> AuthenticationRequestor { get; set; }
|
||||
|
||||
@@ -36,12 +38,12 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
AuthenticationRequestor = authenticationRequestor;
|
||||
}
|
||||
|
||||
public ConnectionTreeModel Deserialize(string xml)
|
||||
public SerializationResult Deserialize(string xml)
|
||||
{
|
||||
return Deserialize(xml, false);
|
||||
}
|
||||
|
||||
public ConnectionTreeModel Deserialize(string xml, bool import)
|
||||
public SerializationResult Deserialize(string xml, bool import)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -49,17 +51,13 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
ValidateConnectionFileVersion();
|
||||
|
||||
var rootXmlElement = _xmlDocument.DocumentElement;
|
||||
InitializeRootNode(rootXmlElement);
|
||||
CreateDecryptor(_rootNodeInfo, rootXmlElement);
|
||||
var connectionTreeModel = new ConnectionTreeModel();
|
||||
connectionTreeModel.AddRootNode(_rootNodeInfo);
|
||||
|
||||
var rootNodeInfo = InitializeRootNode(rootXmlElement);
|
||||
_decryptor = CreateDecryptor(rootNodeInfo, rootXmlElement);
|
||||
|
||||
if (_confVersion > 1.3)
|
||||
{
|
||||
var protectedString = _xmlDocument.DocumentElement?.Attributes["Protected"].Value;
|
||||
if (!_decryptor.ConnectionsFileIsAuthentic(protectedString,
|
||||
_rootNodeInfo.PasswordString.ConvertToSecureString()))
|
||||
if (!_decryptor.ConnectionsFileIsAuthentic(protectedString, rootNodeInfo.PasswordString.ConvertToSecureString()))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -75,12 +73,11 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
}
|
||||
}
|
||||
|
||||
AddNodesFromXmlRecursive(_xmlDocument.DocumentElement, _rootNodeInfo);
|
||||
var credentialMap = new ConnectionToCredentialMap();
|
||||
var rootNodes = AddNodesFromXmlRecursive(_xmlDocument.DocumentElement, credentialMap);
|
||||
var serializationResult = new SerializationResult(rootNodes, credentialMap);
|
||||
|
||||
if (!import)
|
||||
Runtime.ConnectionsService.IsConnectionsFileLoaded = true;
|
||||
|
||||
return connectionTreeModel;
|
||||
return serializationResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -92,8 +89,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
|
||||
private void LoadXmlConnectionData(string connections)
|
||||
{
|
||||
CreateDecryptor(new RootNodeInfo(RootNodeType.Connection));
|
||||
connections = _decryptor.LegacyFullFileDecrypt(connections);
|
||||
var legacyDecryptor = CreateDecryptor(new RootNodeInfo(RootNodeType.Connection));
|
||||
connections = legacyDecryptor.LegacyFullFileDecrypt(connections);
|
||||
_xmlDocument = new XmlDocument();
|
||||
if (connections != "")
|
||||
_xmlDocument.LoadXml(connections);
|
||||
@@ -134,13 +131,16 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
);
|
||||
}
|
||||
|
||||
private void InitializeRootNode(XmlElement connectionsRootElement)
|
||||
private RootNodeInfo InitializeRootNode(XmlElement connectionsRootElement)
|
||||
{
|
||||
var rootNodeName = connectionsRootElement?.Attributes["Name"].Value.Trim();
|
||||
_rootNodeInfo.Name = rootNodeName;
|
||||
return new RootNodeInfo(RootNodeType.Connection)
|
||||
{
|
||||
Name = rootNodeName
|
||||
};
|
||||
}
|
||||
|
||||
private void CreateDecryptor(RootNodeInfo rootNodeInfo, XmlElement connectionsRootElement = null)
|
||||
private XmlConnectionsDecryptor CreateDecryptor(RootNodeInfo rootNodeInfo, XmlElement connectionsRootElement = null)
|
||||
{
|
||||
if (_confVersion >= 2.6)
|
||||
{
|
||||
@@ -148,24 +148,27 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
var mode = connectionsRootElement.GetAttributeAsEnum<BlockCipherModes>("BlockCipherMode");
|
||||
var keyDerivationIterations = connectionsRootElement.GetAttributeAsInt("KdfIterations");
|
||||
|
||||
_decryptor = new XmlConnectionsDecryptor(engine, mode, rootNodeInfo)
|
||||
return new XmlConnectionsDecryptor(engine, mode, rootNodeInfo)
|
||||
{
|
||||
AuthenticationRequestor = AuthenticationRequestor,
|
||||
KeyDerivationIterations = keyDerivationIterations
|
||||
};
|
||||
}
|
||||
else
|
||||
|
||||
return new XmlConnectionsDecryptor(rootNodeInfo)
|
||||
{
|
||||
_decryptor = new XmlConnectionsDecryptor(_rootNodeInfo)
|
||||
{AuthenticationRequestor = AuthenticationRequestor};
|
||||
}
|
||||
AuthenticationRequestor = AuthenticationRequestor
|
||||
};
|
||||
}
|
||||
|
||||
private void AddNodesFromXmlRecursive(XmlNode parentXmlNode, ContainerInfo parentContainer)
|
||||
private List<ConnectionInfo> AddNodesFromXmlRecursive(XmlNode parentXmlNode, ConnectionToCredentialMap credentialMap)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!parentXmlNode.HasChildNodes) return;
|
||||
if (!parentXmlNode.HasChildNodes)
|
||||
return new List<ConnectionInfo>();
|
||||
|
||||
var children = new List<ConnectionInfo>();
|
||||
foreach (XmlNode xmlNode in parentXmlNode.ChildNodes)
|
||||
{
|
||||
var nodeType = xmlNode.GetAttributeAsEnum("Type", TreeNodeType.Connection);
|
||||
@@ -174,24 +177,27 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
switch (nodeType)
|
||||
{
|
||||
case TreeNodeType.Connection:
|
||||
var connectionInfo = GetConnectionInfoFromXml(xmlNode);
|
||||
parentContainer.AddChild(connectionInfo);
|
||||
var connectionInfo = GetConnectionInfoFromXml(xmlNode, credentialMap);
|
||||
children.Add(connectionInfo);
|
||||
break;
|
||||
case TreeNodeType.Container:
|
||||
var containerInfo = new ContainerInfo();
|
||||
|
||||
if (_confVersion >= 0.9)
|
||||
containerInfo.CopyFrom(GetConnectionInfoFromXml(xmlNode));
|
||||
containerInfo.CopyFrom(GetConnectionInfoFromXml(xmlNode, credentialMap));
|
||||
if (_confVersion >= 0.8)
|
||||
{
|
||||
containerInfo.IsExpanded = xmlNode.GetAttributeAsBool("Expanded");
|
||||
}
|
||||
|
||||
parentContainer.AddChild(containerInfo);
|
||||
AddNodesFromXmlRecursive(xmlNode, containerInfo);
|
||||
var subChildren = AddNodesFromXmlRecursive(xmlNode, credentialMap);
|
||||
subChildren.ForEach(info => containerInfo.AddChild(info));
|
||||
children.Add(containerInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -200,7 +206,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
}
|
||||
}
|
||||
|
||||
private ConnectionInfo GetConnectionInfoFromXml(XmlNode xmlnode)
|
||||
private ConnectionInfo GetConnectionInfoFromXml(XmlNode xmlnode, ConnectionToCredentialMap credentialMap)
|
||||
{
|
||||
if (xmlnode?.Attributes == null)
|
||||
return null;
|
||||
@@ -228,13 +234,21 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
: RDPResolutions.FitToWindow;
|
||||
}
|
||||
|
||||
if (!Runtime.UseCredentialManager || _confVersion <= 2.6) // 0.2 - 2.6
|
||||
if (_confVersion <= 2.6) // 0.2 - 2.6
|
||||
{
|
||||
#pragma warning disable 618
|
||||
connectionInfo.Username = xmlnode.GetAttributeAsString("Username");
|
||||
connectionInfo.Password = _decryptor.Decrypt(xmlnode.GetAttributeAsString("Password"));
|
||||
connectionInfo.Domain = xmlnode.GetAttributeAsString("Domain");
|
||||
#pragma warning restore 618
|
||||
var username = xmlnode.GetAttributeAsString("Username");
|
||||
var domain = xmlnode.GetAttributeAsString("Domain");
|
||||
|
||||
var cred = new CredentialRecord
|
||||
{
|
||||
Title = domain.Length > 0 ? $"{domain}\\{username}" : username,
|
||||
Username = username,
|
||||
Domain = domain,
|
||||
Password = _decryptor.Decrypt(xmlnode.GetAttributeAsString("Password")).ConvertToSecureString()
|
||||
};
|
||||
|
||||
if (!_credentialComparer.Equals(cred, new NullCredentialRecord()))
|
||||
connectionInfo.CredentialRecordId = cred.Id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,6 +570,12 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
connectionInfo.Inheritance.DisableMenuAnimations = xmlnode.GetAttributeAsBool("InheritDisableMenuAnimations");
|
||||
connectionInfo.Inheritance.DisableCursorShadow = xmlnode.GetAttributeAsBool("InheritDisableCursorShadow");
|
||||
connectionInfo.Inheritance.DisableCursorBlinking = xmlnode.GetAttributeAsBool("InheritDisableCursorBlinking");
|
||||
|
||||
connectionInfo.CredentialRecordId = Guid.TryParse(xmlnode.Attributes?["CredentialId"]?.Value, out var credId)
|
||||
? credId
|
||||
: Optional<Guid>.Empty;
|
||||
|
||||
connectionInfo.Inheritance.CredentialId = xmlnode.GetAttributeAsBool("InheritCredentialId");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -12,8 +12,7 @@ using mRemoteNG.Tree.Root;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
{
|
||||
public class XmlConnectionsSerializer : ISerializer<ConnectionTreeModel, string>,
|
||||
ISerializer<ConnectionInfo, string>
|
||||
public class XmlConnectionsSerializer : ISerializer<IConnectionTreeModel,string>, ISerializer<ConnectionInfo, string>
|
||||
{
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
private readonly ISerializer<ConnectionInfo, XElement> _connectionNodeSerializer;
|
||||
@@ -28,7 +27,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
|
||||
_connectionNodeSerializer = connectionNodeSerializer;
|
||||
}
|
||||
|
||||
public string Serialize(ConnectionTreeModel connectionTreeModel)
|
||||
public string Serialize(IConnectionTreeModel connectionTreeModel)
|
||||
{
|
||||
var rootNode = (RootNodeInfo)connectionTreeModel.RootNodes.First(node => node is RootNodeInfo);
|
||||
return SerializeConnectionsData(rootNode);
|
||||
|
||||
@@ -9,29 +9,38 @@ namespace mRemoteNG.Config.Serializers.CredentialProviderSerializer
|
||||
{
|
||||
public class CredentialRepositoryListDeserializer
|
||||
{
|
||||
private readonly ISecureSerializer<IEnumerable<ICredentialRecord>, string> _serializer;
|
||||
private readonly ISecureDeserializer<string, IEnumerable<ICredentialRecord>> _deserializer;
|
||||
|
||||
public CredentialRepositoryListDeserializer(
|
||||
ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer,
|
||||
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer)
|
||||
public IEnumerable<ICredentialRepository> Deserialize(string xml, IEnumerable<ICredentialRepositoryFactory> factories)
|
||||
{
|
||||
if (serializer == null)
|
||||
throw new ArgumentNullException(nameof(serializer));
|
||||
if (deserializer == null)
|
||||
throw new ArgumentNullException(nameof(deserializer));
|
||||
if (string.IsNullOrEmpty(xml))
|
||||
return new ICredentialRepository[0];
|
||||
|
||||
_serializer = serializer;
|
||||
_deserializer = deserializer;
|
||||
}
|
||||
|
||||
public IEnumerable<ICredentialRepository> Deserialize(string xml)
|
||||
{
|
||||
if (string.IsNullOrEmpty(xml)) return new ICredentialRepository[0];
|
||||
var xdoc = XDocument.Parse(xml);
|
||||
var repoEntries = xdoc.Descendants("CredentialRepository");
|
||||
var xmlRepoFactory = new XmlCredentialRepositoryFactory(_serializer, _deserializer);
|
||||
return repoEntries.Select(xmlRepoFactory.Build);
|
||||
|
||||
return repoEntries
|
||||
.Select(ParseConfigEntries)
|
||||
.Select(config =>
|
||||
factories
|
||||
.FirstOrDefault(f => string.Equals(f.SupportsConfigType, config.TypeName))?
|
||||
.Build(config));
|
||||
}
|
||||
|
||||
public ICredentialRepositoryConfig ParseConfigEntries(XElement repositoryXElement)
|
||||
{
|
||||
var stringId = repositoryXElement.Attribute("Id")?.Value;
|
||||
Guid.TryParse(stringId, out var id);
|
||||
|
||||
if (id.Equals(Guid.Empty))
|
||||
id = Guid.NewGuid();
|
||||
|
||||
var config = new CredentialRepositoryConfig(id)
|
||||
{
|
||||
TypeName = repositoryXElement.Attribute("TypeName")?.Value,
|
||||
Title = repositoryXElement.Attribute("Title")?.Value,
|
||||
Source = repositoryXElement.Attribute("Source")?.Value
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Credential;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.CredentialSerializer
|
||||
{
|
||||
public class CredentialHarvester
|
||||
{
|
||||
private readonly IEqualityComparer<ICredentialRecord> _credentialComparer = new CredentialDomainUserPasswordComparer();
|
||||
|
||||
/// <summary>
|
||||
/// Maps a <see cref="ConnectionInfo"/> (by its id) to the <see cref="ICredentialRecord"/>
|
||||
/// object that was harvested
|
||||
/// </summary>
|
||||
/// <param name="harvestConfig"></param>
|
||||
public ConnectionToCredentialMap Harvest<T>(HarvestConfig<T> harvestConfig)
|
||||
{
|
||||
if (harvestConfig == null)
|
||||
throw new ArgumentNullException(nameof(harvestConfig));
|
||||
|
||||
var credentialMap = new ConnectionToCredentialMap();
|
||||
foreach (var element in harvestConfig.ItemEnumerator())
|
||||
{
|
||||
var newCredential = new CredentialRecord
|
||||
{
|
||||
Title = harvestConfig.TitleSelector(element),
|
||||
Username = harvestConfig.UsernameSelector(element),
|
||||
Domain = harvestConfig.DomainSelector(element),
|
||||
Password = harvestConfig.PasswordSelector(element)
|
||||
};
|
||||
|
||||
if (!EntryHasSomeCredentialData(newCredential))
|
||||
continue;
|
||||
|
||||
var connectionId = harvestConfig.ConnectionGuidSelector(element);
|
||||
|
||||
var existingCredential = credentialMap.Values.FirstOrDefault(record => _credentialComparer.Equals(newCredential, record));
|
||||
credentialMap.Add(connectionId, existingCredential ?? newCredential);
|
||||
}
|
||||
|
||||
return credentialMap;
|
||||
}
|
||||
|
||||
private bool EntryHasSomeCredentialData(ICredentialRecord e)
|
||||
{
|
||||
return !_credentialComparer.Equals(e, new NullCredentialRecord());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.CredentialSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration for the <see cref="CredentialHarvester"/> to allow it to
|
||||
/// iterate over and select values from any arbitrary data type. Each element
|
||||
/// of type <see cref="T"/> represents an object that contains intermixed
|
||||
/// connection and credential data.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class HarvestConfig<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// This will be called to produce a list of all objects
|
||||
/// that should be iterated over.
|
||||
/// </summary>
|
||||
public Func<IEnumerable<T>> ItemEnumerator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Given an item of type <see cref="T"/>, return
|
||||
/// the <see cref="Guid"/> that represents the connection's unique ID
|
||||
/// within mRemoteNG.
|
||||
/// </summary>
|
||||
public Func<T, Guid> ConnectionGuidSelector { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Given an item of type <see cref="T"/>, return a <see cref="string"/>
|
||||
/// that represents what the associated credential's title should be.
|
||||
/// </summary>
|
||||
public Func<T, string> TitleSelector { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Given an item of type <see cref="T"/>, return a <see cref="string"/>
|
||||
/// that represents what the associated credential's username should be.
|
||||
/// </summary>
|
||||
public Func<T, string> UsernameSelector { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Given an item of type <see cref="T"/>, return a <see cref="string"/>
|
||||
/// that represents what the associated credential's domain should be.
|
||||
/// </summary>
|
||||
public Func<T, string> DomainSelector { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Given an item of type <see cref="T"/>, return a <see cref="SecureString"/>
|
||||
/// that represents what the associated credential's password should be.
|
||||
/// </summary>
|
||||
public Func<T, SecureString> PasswordSelector { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,68 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Container;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
using System.Xml;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.Security;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
{
|
||||
public class PuttyConnectionManagerDeserializer : IDeserializer<string, ConnectionTreeModel>
|
||||
public class PuttyConnectionManagerDeserializer
|
||||
{
|
||||
public ConnectionTreeModel Deserialize(string puttycmConnectionsXml)
|
||||
public SerializationResult Deserialize(string puttycmConnectionsXml)
|
||||
{
|
||||
var connectionTreeModel = new ConnectionTreeModel();
|
||||
var root = new RootNodeInfo(RootNodeType.Connection);
|
||||
connectionTreeModel.AddRootNode(root);
|
||||
var result = new SerializationResult(new List<ConnectionInfo>(), new ConnectionToCredentialMap());
|
||||
|
||||
var xmlDocument = new XmlDocument();
|
||||
xmlDocument.LoadXml(puttycmConnectionsXml);
|
||||
|
||||
var configurationNode = xmlDocument.SelectSingleNode("/configuration");
|
||||
|
||||
var rootNodes = configurationNode?.SelectNodes("./root");
|
||||
if (rootNodes == null) return connectionTreeModel;
|
||||
foreach (XmlNode rootNode in rootNodes)
|
||||
var rootXmlNode = configurationNode?.SelectSingleNode("./root");
|
||||
if (rootXmlNode == null)
|
||||
return result;
|
||||
|
||||
var rootContainer = ReadContainerProperties(rootXmlNode);
|
||||
result.ConnectionRecords.Add(rootContainer);
|
||||
|
||||
foreach (XmlNode node in rootXmlNode.ChildNodes)
|
||||
{
|
||||
ImportRootOrContainer(rootNode, root);
|
||||
rootContainer.AddChild(ImportRecursive(node, result.ConnectionToCredentialMap));
|
||||
}
|
||||
|
||||
return connectionTreeModel;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ImportRootOrContainer(XmlNode xmlNode, ContainerInfo parentContainer)
|
||||
private ContainerInfo ImportRecursive(XmlNode xmlNode, ConnectionToCredentialMap credentialMap)
|
||||
{
|
||||
VerifyNodeType(xmlNode);
|
||||
|
||||
var newContainer = ImportContainer(xmlNode, parentContainer);
|
||||
var newContainer = ReadContainerProperties(xmlNode);
|
||||
|
||||
var childNodes = xmlNode.SelectNodes("./*");
|
||||
if (childNodes == null) return;
|
||||
if (childNodes == null)
|
||||
return newContainer;
|
||||
|
||||
foreach (XmlNode childNode in childNodes)
|
||||
{
|
||||
switch (childNode.Name)
|
||||
{
|
||||
case "container":
|
||||
ImportRootOrContainer(childNode, newContainer);
|
||||
newContainer.AddChild(ImportRecursive(childNode, credentialMap));
|
||||
break;
|
||||
case "connection":
|
||||
ImportConnection(childNode, newContainer);
|
||||
newContainer.AddChild(ImportConnection(childNode, credentialMap));
|
||||
break;
|
||||
default:
|
||||
throw (new FileFormatException($"Unrecognized child node ({childNode.Name})."));
|
||||
throw new FileFormatException($"Unrecognized child node ({childNode.Name}).");
|
||||
}
|
||||
}
|
||||
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
private void VerifyNodeType(XmlNode xmlNode)
|
||||
@@ -82,25 +91,28 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
}
|
||||
}
|
||||
|
||||
private ContainerInfo ImportContainer(XmlNode containerNode, ContainerInfo parentContainer)
|
||||
private ContainerInfo ReadContainerProperties(XmlNode containerNode)
|
||||
{
|
||||
var containerInfo = new ContainerInfo
|
||||
{
|
||||
Name = containerNode.Attributes?["name"].Value,
|
||||
IsExpanded = bool.Parse(containerNode.Attributes?["expanded"].InnerText ?? "false")
|
||||
};
|
||||
parentContainer.AddChild(containerInfo);
|
||||
|
||||
return containerInfo;
|
||||
}
|
||||
|
||||
private void ImportConnection(XmlNode connectionNode, ContainerInfo parentContainer)
|
||||
private ConnectionInfo ImportConnection(XmlNode connectionNode, ConnectionToCredentialMap credentialMap)
|
||||
{
|
||||
var connectionNodeType = connectionNode.Attributes?["type"].Value;
|
||||
if (string.Compare(connectionNodeType, "PuTTY", StringComparison.OrdinalIgnoreCase) != 0)
|
||||
throw (new FileFormatException($"Unrecognized connection node type ({connectionNodeType})."));
|
||||
|
||||
var connectionInfo = ConnectionInfoFromXml(connectionNode);
|
||||
parentContainer.AddChild(connectionInfo);
|
||||
var cred = CredentialFromXml(connectionNode);
|
||||
connectionInfo.CredentialRecordId = cred.Id;
|
||||
credentialMap.Add(Guid.Parse(connectionInfo.ConstantID), cred);
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
private ConnectionInfo ConnectionInfoFromXml(XmlNode xmlNode)
|
||||
@@ -129,9 +141,6 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
// ./commandline
|
||||
connectionInfo.Description = connectionInfoNode.SelectSingleNode("./description")?.InnerText;
|
||||
|
||||
var loginNode = xmlNode.SelectSingleNode("./login");
|
||||
connectionInfo.Username = loginNode?.SelectSingleNode("login")?.InnerText;
|
||||
connectionInfo.Password = loginNode?.SelectSingleNode("password")?.InnerText;
|
||||
// ./prompt
|
||||
|
||||
// ./timeout/connectiontimeout
|
||||
@@ -151,5 +160,19 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
private ICredentialRecord CredentialFromXml(XmlNode xmlNode)
|
||||
{
|
||||
var loginNode = xmlNode.SelectSingleNode("./login");
|
||||
var username = loginNode?.SelectSingleNode("login")?.InnerText ?? "";
|
||||
|
||||
return new CredentialRecord
|
||||
{
|
||||
Title = username,
|
||||
Username = username,
|
||||
Domain = "",
|
||||
Password = loginNode?.SelectSingleNode("password")?.InnerText.ConvertToSecureString() ?? new SecureString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
using System;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol.RDP;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
using System.Collections.Generic;
|
||||
using mRemoteNG.Credential;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
{
|
||||
public class RemoteDesktopConnectionDeserializer : IDeserializer<string, ConnectionTreeModel>
|
||||
public class RemoteDesktopConnectionDeserializer
|
||||
{
|
||||
// .rdp file schema: https://technet.microsoft.com/en-us/library/ff393699(v=ws.10).aspx
|
||||
|
||||
public ConnectionTreeModel Deserialize(string rdcFileContent)
|
||||
public SerializationResult Deserialize(string rdcFileContent)
|
||||
{
|
||||
var connectionTreeModel = new ConnectionTreeModel();
|
||||
var root = new RootNodeInfo(RootNodeType.Connection);
|
||||
connectionTreeModel.AddRootNode(root);
|
||||
var connectionInfo = new ConnectionInfo();
|
||||
var username = "";
|
||||
var domain = "";
|
||||
|
||||
foreach (var line in rdcFileContent.Split(Environment.NewLine.ToCharArray()))
|
||||
{
|
||||
var parts = line.Split(new[] { ':' }, 3);
|
||||
@@ -24,21 +24,42 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = parts[0].Trim();
|
||||
var propertyName = parts[0].Trim().ToLowerInvariant();
|
||||
var value = parts[2].Trim();
|
||||
|
||||
SetConnectionInfoParameter(connectionInfo, key, value);
|
||||
SetConnectionInfoParameter(connectionInfo, propertyName, value);
|
||||
|
||||
|
||||
if (propertyName.Equals("username"))
|
||||
username = value;
|
||||
if (propertyName.Equals("domain"))
|
||||
domain = value;
|
||||
}
|
||||
|
||||
root.AddChild(connectionInfo);
|
||||
var serializationResult = new SerializationResult(new List<ConnectionInfo>(), new ConnectionToCredentialMap());
|
||||
serializationResult.ConnectionRecords.Add(connectionInfo);
|
||||
|
||||
return connectionTreeModel;
|
||||
if (username.Length > 0 || domain.Length > 0)
|
||||
{
|
||||
var cred = new CredentialRecord
|
||||
{
|
||||
Title = domain.Length > 0 ? $"{domain}\\" : "" + username,
|
||||
Domain = domain,
|
||||
Username = username
|
||||
};
|
||||
|
||||
serializationResult.ConnectionToCredentialMap.Add(Guid.Parse(connectionInfo.ConstantID), cred);
|
||||
connectionInfo.CredentialRecordId = cred.Id;
|
||||
}
|
||||
|
||||
|
||||
return serializationResult;
|
||||
}
|
||||
|
||||
|
||||
private void SetConnectionInfoParameter(ConnectionInfo connectionInfo, string key, string value)
|
||||
{
|
||||
switch (key.ToLower())
|
||||
switch (key)
|
||||
{
|
||||
case "full address":
|
||||
var uri = new Uri("dummyscheme" + Uri.SchemeDelimiter + value);
|
||||
@@ -50,12 +71,6 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
case "server port":
|
||||
connectionInfo.Port = Convert.ToInt32(value);
|
||||
break;
|
||||
case "username":
|
||||
connectionInfo.Username = value;
|
||||
break;
|
||||
case "domain":
|
||||
connectionInfo.Domain = value;
|
||||
break;
|
||||
case "session bpp":
|
||||
switch (value)
|
||||
{
|
||||
|
||||
@@ -1,40 +1,44 @@
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Connection.Protocol.RDP;
|
||||
using mRemoteNG.Container;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Connection.Protocol.RDP;
|
||||
using mRemoteNG.Container;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
{
|
||||
public class RemoteDesktopConnectionManagerDeserializer : IDeserializer<string, ConnectionTreeModel>
|
||||
public class RemoteDesktopConnectionManagerDeserializer
|
||||
{
|
||||
private static int _schemaVersion; /* 1 = RDCMan v2.2
|
||||
3 = RDCMan v2.7 */
|
||||
// 1 = RDCMan v2.2
|
||||
// 3 = RDCMan v2.7
|
||||
private static int _schemaVersion;
|
||||
|
||||
public ConnectionTreeModel Deserialize(string rdcmConnectionsXml)
|
||||
public SerializationResult Deserialize(string rdcmConnectionsXml)
|
||||
{
|
||||
var connectionTreeModel = new ConnectionTreeModel();
|
||||
var root = new RootNodeInfo(RootNodeType.Connection);
|
||||
var serializationResult = new SerializationResult(new List<ConnectionInfo>(), new ConnectionToCredentialMap());
|
||||
|
||||
var xmlDocument = new XmlDocument();
|
||||
xmlDocument.LoadXml(rdcmConnectionsXml);
|
||||
|
||||
|
||||
var rdcManNode = xmlDocument.SelectSingleNode("/RDCMan");
|
||||
VerifySchemaVersion(rdcManNode);
|
||||
VerifyFileVersion(rdcManNode);
|
||||
|
||||
var fileNode = rdcManNode?.SelectSingleNode("./file");
|
||||
ImportFileOrGroup(fileNode, root);
|
||||
var importedItem = ImportFileOrGroup(fileNode, serializationResult.ConnectionToCredentialMap);
|
||||
|
||||
connectionTreeModel.AddRootNode(root);
|
||||
return connectionTreeModel;
|
||||
serializationResult.ConnectionRecords.Add(importedItem);
|
||||
|
||||
return serializationResult;
|
||||
}
|
||||
|
||||
private static void VerifySchemaVersion(XmlNode rdcManNode)
|
||||
@@ -79,28 +83,46 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
}
|
||||
}
|
||||
|
||||
private static void ImportFileOrGroup(XmlNode xmlNode, ContainerInfo parentContainer)
|
||||
private static ContainerInfo ImportFileOrGroup(XmlNode xmlNode, ConnectionToCredentialMap credentialMap)
|
||||
{
|
||||
var newContainer = ImportContainer(xmlNode, parentContainer);
|
||||
var newContainer = ImportContainer(xmlNode);
|
||||
|
||||
var childNodes = xmlNode.SelectNodes("./group|./server");
|
||||
if (childNodes == null) return;
|
||||
if (childNodes == null)
|
||||
return newContainer;
|
||||
|
||||
foreach (XmlNode childNode in childNodes)
|
||||
{
|
||||
ConnectionInfo newChild = null;
|
||||
|
||||
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||
switch (childNode.Name)
|
||||
{
|
||||
case "group":
|
||||
ImportFileOrGroup(childNode, newContainer);
|
||||
newChild = ImportFileOrGroup(childNode, credentialMap);
|
||||
break;
|
||||
case "server":
|
||||
ImportServer(childNode, newContainer);
|
||||
newChild = ConnectionInfoFromXml(childNode);
|
||||
break;
|
||||
}
|
||||
|
||||
if (newChild == null)
|
||||
return newContainer;
|
||||
|
||||
newContainer.AddChild(newChild);
|
||||
|
||||
var cred = ParseCredentials(childNode);
|
||||
if (!cred.Any())
|
||||
continue;
|
||||
|
||||
newChild.CredentialRecordId = cred.First().Id;
|
||||
credentialMap.Add(Guid.Parse(newChild.ConstantID), cred.First());
|
||||
}
|
||||
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
private static ContainerInfo ImportContainer(XmlNode containerPropertiesNode, ContainerInfo parentContainer)
|
||||
private static ContainerInfo ImportContainer(XmlNode containerPropertiesNode)
|
||||
{
|
||||
if (_schemaVersion == 1)
|
||||
{
|
||||
@@ -120,16 +142,9 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
newContainer.Name = containerPropertiesNode?.SelectSingleNode("./name")?.InnerText ?? Language.NewFolder;
|
||||
if (bool.TryParse(containerPropertiesNode?.SelectSingleNode("./expanded")?.InnerText, out var expanded))
|
||||
newContainer.IsExpanded = expanded;
|
||||
parentContainer.AddChild(newContainer);
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
private static void ImportServer(XmlNode serverNode, ContainerInfo parentContainer)
|
||||
{
|
||||
var newConnectionInfo = ConnectionInfoFromXml(serverNode);
|
||||
parentContainer.AddChild(newConnectionInfo);
|
||||
}
|
||||
|
||||
private static ConnectionInfo ConnectionInfoFromXml(XmlNode xmlNode)
|
||||
{
|
||||
var connectionInfo = new ConnectionInfo {Protocol = ProtocolType.RDP};
|
||||
@@ -152,25 +167,7 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
connectionInfo.Description = propertiesNode?.SelectSingleNode("./comment")?.InnerText ?? string.Empty;
|
||||
|
||||
var logonCredentialsNode = xmlNode.SelectSingleNode("./logonCredentials");
|
||||
if (logonCredentialsNode?.Attributes?["inherit"]?.Value == "None")
|
||||
{
|
||||
connectionInfo.Username = logonCredentialsNode.SelectSingleNode("userName")?.InnerText ?? string.Empty;
|
||||
|
||||
var passwordNode = logonCredentialsNode.SelectSingleNode("./password");
|
||||
if (_schemaVersion == 1) // Version 2.2 allows clear text passwords
|
||||
{
|
||||
connectionInfo.Password = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
|
||||
? passwordNode.InnerText
|
||||
: DecryptRdcManPassword(passwordNode?.InnerText);
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionInfo.Password = DecryptRdcManPassword(passwordNode?.InnerText);
|
||||
}
|
||||
|
||||
connectionInfo.Domain = logonCredentialsNode.SelectSingleNode("./domain")?.InnerText ?? string.Empty;
|
||||
}
|
||||
else
|
||||
if (logonCredentialsNode?.Attributes?["inherit"]?.Value != "None")
|
||||
{
|
||||
connectionInfo.Inheritance.Username = true;
|
||||
connectionInfo.Inheritance.Password = true;
|
||||
@@ -204,9 +201,9 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
connectionInfo.RDGatewayUsername = gatewaySettingsNode.SelectSingleNode("./userName")?.InnerText ?? string.Empty;
|
||||
|
||||
var passwordNode = gatewaySettingsNode.SelectSingleNode("./password");
|
||||
connectionInfo.RDGatewayPassword = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
|
||||
? passwordNode.InnerText
|
||||
: DecryptRdcManPassword(passwordNode?.InnerText);
|
||||
connectionInfo.RDGatewayPassword = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
|
||||
? passwordNode.InnerText
|
||||
: DecryptRdcManPassword(passwordNode?.InnerText).ConvertToUnsecureString();
|
||||
|
||||
connectionInfo.RDGatewayDomain = gatewaySettingsNode.SelectSingleNode("./domain")?.InnerText ?? string.Empty;
|
||||
// ./logonMethod
|
||||
@@ -348,22 +345,45 @@ namespace mRemoteNG.Config.Serializers.MiscSerializers
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
private static string DecryptRdcManPassword(string ciphertext)
|
||||
private static Optional<ICredentialRecord> ParseCredentials(XmlNode xmlNode)
|
||||
{
|
||||
var logonCredentialsNode = xmlNode.SelectSingleNode("./logonCredentials");
|
||||
|
||||
if (logonCredentialsNode?.Attributes?["inherit"]?.Value != "None")
|
||||
return Optional<ICredentialRecord>.Empty;
|
||||
|
||||
var username = logonCredentialsNode.SelectSingleNode("userName")?.InnerText ?? "";
|
||||
var domain = logonCredentialsNode.SelectSingleNode("./domain")?.InnerText ?? "";
|
||||
var passwordNode = logonCredentialsNode.SelectSingleNode("./password");
|
||||
|
||||
var creds = new CredentialRecord
|
||||
{
|
||||
Title = domain.Length > 0 ? $"{domain}\\{username}" : $"{username}",
|
||||
Username = username,
|
||||
Domain = domain,
|
||||
Password = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True"
|
||||
? passwordNode.InnerText.ConvertToSecureString()
|
||||
: DecryptRdcManPassword(passwordNode?.InnerText)
|
||||
};
|
||||
|
||||
return creds;
|
||||
}
|
||||
|
||||
private static SecureString DecryptRdcManPassword(string ciphertext)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ciphertext))
|
||||
return string.Empty;
|
||||
return new SecureString();
|
||||
|
||||
try
|
||||
{
|
||||
var plaintextData = ProtectedData.Unprotect(Convert.FromBase64String(ciphertext), new byte[] { },
|
||||
DataProtectionScope.LocalMachine);
|
||||
var charArray = Encoding.Unicode.GetChars(plaintextData);
|
||||
return new string(charArray);
|
||||
return Encoding.Unicode.GetString(plaintextData).ConvertToSecureString();
|
||||
}
|
||||
catch (Exception /*ex*/)
|
||||
{
|
||||
//Runtime.MessageCollector.AddExceptionMessage("RemoteDesktopConnectionManager.DecryptPassword() failed.", ex, logOnly: true);
|
||||
return string.Empty;
|
||||
return new SecureString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
mRemoteNG/Config/Serializers/SerializationResult.cs
Normal file
23
mRemoteNG/Config/Serializers/SerializationResult.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Tools;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the connections and credentials found during a deserialization.
|
||||
/// </summary>
|
||||
public class SerializationResult
|
||||
{
|
||||
public List<ConnectionInfo> ConnectionRecords { get; }
|
||||
public ConnectionToCredentialMap ConnectionToCredentialMap { get; }
|
||||
|
||||
public SerializationResult(
|
||||
List<ConnectionInfo> connectionRecords,
|
||||
ConnectionToCredentialMap connectionToCredentialMap)
|
||||
{
|
||||
ConnectionRecords = connectionRecords.ThrowIfNull(nameof(connectionRecords));
|
||||
ConnectionToCredentialMap = connectionToCredentialMap.ThrowIfNull(nameof(connectionToCredentialMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Config.Serializers.CredentialSerializer;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Security.Authentication;
|
||||
using mRemoteNG.Security.Factories;
|
||||
using mRemoteNG.Tools;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
|
||||
|
||||
namespace mRemoteNG.Config.Serializers.Versioning
|
||||
{
|
||||
public class XmlCredentialManagerUpgrader
|
||||
{
|
||||
private readonly XmlConnectionsDeserializer _deserializer;
|
||||
|
||||
|
||||
public XmlCredentialManagerUpgrader(XmlConnectionsDeserializer decoratedDeserializer)
|
||||
{
|
||||
_deserializer = decoratedDeserializer.ThrowIfNull(nameof(decoratedDeserializer));
|
||||
}
|
||||
|
||||
public SerializationResult Deserialize(string serializedData, ConnectionToCredentialMap upgradeMap)
|
||||
{
|
||||
var serializedDataAsXDoc = EnsureConnectionXmlElementsHaveIds(serializedData);
|
||||
var serializedDataWithIds = $"{serializedDataAsXDoc.Declaration}{serializedDataAsXDoc}";
|
||||
|
||||
var serializationResult = _deserializer.Deserialize(serializedDataWithIds);
|
||||
|
||||
if (upgradeMap != null)
|
||||
ApplyCredentialMapping(upgradeMap, serializationResult.ConnectionRecords.FlattenConnectionTree());
|
||||
|
||||
return serializationResult;
|
||||
}
|
||||
|
||||
private XDocument EnsureConnectionXmlElementsHaveIds(string serializedData)
|
||||
{
|
||||
var xdoc = XDocument.Parse(serializedData);
|
||||
xdoc.Declaration = new XDeclaration("1.0", "utf-8", null);
|
||||
var adapter = new ConfConsEnsureConnectionsHaveIds();
|
||||
adapter.EnsureElementsHaveIds(xdoc);
|
||||
return xdoc;
|
||||
}
|
||||
|
||||
public ConnectionToCredentialMap UpgradeUserFilesForCredentialManager(XDocument xdoc)
|
||||
{
|
||||
if (!CredentialManagerUpgradeNeeded(xdoc))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var cryptoProvider = new CryptoProviderFactoryFromXml(xdoc.Root).Build();
|
||||
var encryptedValue = xdoc.Root?.Attribute("Protected")?.Value;
|
||||
var auth = new PasswordAuthenticator(cryptoProvider, encryptedValue, () => MiscTools.PasswordDialog("", false));
|
||||
if (!auth.Authenticate(Runtime.EncryptionKey))
|
||||
throw new Exception("Could not authenticate");
|
||||
|
||||
var keyForOldConnectionFile = auth.LastAuthenticatedPassword;
|
||||
|
||||
var preCredManagerXmlHarvestConfig = new HarvestConfig<XElement>
|
||||
{
|
||||
ItemEnumerator = () => xdoc.Descendants("Node"),
|
||||
ConnectionGuidSelector = e =>
|
||||
{
|
||||
Guid.TryParse(e.Attribute("Id")?.Value, out var connectionId);
|
||||
return connectionId;
|
||||
},
|
||||
UsernameSelector = e => e.Attribute("Username")?.Value,
|
||||
DomainSelector = e => e.Attribute("Domain")?.Value,
|
||||
PasswordSelector = e => cryptoProvider.Decrypt(e.Attribute("Password")?.Value, keyForOldConnectionFile).ConvertToSecureString(),
|
||||
TitleSelector = e => $"{e.Attribute("Domain")?.Value}{(string.IsNullOrEmpty(e.Attribute("Domain")?.Value) ? "" : "\\")}{e.Attribute("Username")?.Value}"
|
||||
};
|
||||
|
||||
var credentialHarvester = new CredentialHarvester();
|
||||
var harvestedCredentials = credentialHarvester.Harvest(preCredManagerXmlHarvestConfig);
|
||||
|
||||
return harvestedCredentials;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If any connections in the xml contain a Username/Domain/Password field, we need to upgrade
|
||||
/// it to be compatible with the credential manager.
|
||||
/// </summary>
|
||||
/// <param name="xdoc"></param>
|
||||
public static bool CredentialManagerUpgradeNeeded(XContainer xdoc)
|
||||
{
|
||||
return xdoc
|
||||
.Descendants("Node")
|
||||
.Any(n =>
|
||||
n.Attribute("Username") != null ||
|
||||
n.Attribute("Domain") != null ||
|
||||
n.Attribute("Password") != null);
|
||||
}
|
||||
|
||||
private void ApplyCredentialMapping(IDictionary<Guid, ICredentialRecord> map, IEnumerable<AbstractConnectionRecord> connectionRecords)
|
||||
{
|
||||
foreach (var connectionInfo in connectionRecords)
|
||||
{
|
||||
Guid.TryParse(connectionInfo.ConstantID, out var id);
|
||||
if (map.ContainsKey(id))
|
||||
connectionInfo.CredentialRecordId = map[id].Id.ToOptional();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing.Design;
|
||||
using System.Linq;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Connection.Protocol.Http;
|
||||
using mRemoteNG.Connection.Protocol.RDP;
|
||||
using mRemoteNG.Connection.Protocol.VNC;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.Properties;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tools.Attributes;
|
||||
using mRemoteNG.UI.Controls.Adapters;
|
||||
|
||||
|
||||
namespace mRemoteNG.Connection
|
||||
@@ -21,6 +27,7 @@ namespace mRemoteNG.Connection
|
||||
private string _panel;
|
||||
|
||||
private string _hostname;
|
||||
private Optional<Guid> _credentialRecordId = new Optional<Guid>();
|
||||
private string _username = "";
|
||||
private string _password = "";
|
||||
private string _domain = "";
|
||||
@@ -159,6 +166,7 @@ namespace mRemoteNG.Connection
|
||||
set => SetField(ref _port, value, "Port");
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
|
||||
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Username)),
|
||||
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionUsername)),
|
||||
@@ -169,6 +177,7 @@ namespace mRemoteNG.Connection
|
||||
set => SetField(ref _username, Settings.Default.DoNotTrimUsername ? value : value?.Trim(), "Username");
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
|
||||
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Password)),
|
||||
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionPassword)),
|
||||
@@ -180,6 +189,7 @@ namespace mRemoteNG.Connection
|
||||
set => SetField(ref _password, value, "Password");
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
|
||||
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Domain)),
|
||||
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionDomain)),
|
||||
@@ -210,6 +220,26 @@ namespace mRemoteNG.Connection
|
||||
get => GetPropertyValue("SSHTunnelConnectionName", _sshTunnelConnectionName).Trim();
|
||||
set => SetField(ref _sshTunnelConnectionName, value?.Trim(), "SSHTunnelConnectionName");
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public virtual Optional<Guid> CredentialRecordId
|
||||
{
|
||||
get => GetPropertyValue(nameof(CredentialRecordId), _credentialRecordId);
|
||||
set => SetField(ref _credentialRecordId, value ?? Optional<Guid>.Empty, nameof(CredentialRecordId));
|
||||
}
|
||||
|
||||
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 2),
|
||||
LocalizedAttributes.LocalizedDisplayName(nameof(Language.Credentials)),
|
||||
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionCredentials)),
|
||||
AttributeUsedInAllProtocolsExcept()]
|
||||
[Editor(typeof(CredentialRecordListAdaptor), typeof(UITypeEditor))]
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public virtual ICredentialRecord CredentialRecord
|
||||
{
|
||||
// TODO: this static ref to the cred service makes testing difficult. refactor it to allow easy mocking
|
||||
get => Runtime.CredentialService.GetEffectiveCredentialRecord(CredentialRecordId, false).FirstOrDefault();
|
||||
set => CredentialRecordId = Optional<Guid>.FromNullable(value?.Id);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Protocol
|
||||
|
||||
@@ -135,22 +135,24 @@ namespace mRemoteNG.Connection
|
||||
return filteredProperties;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<PropertyInfo> GetSerializableProperties()
|
||||
{
|
||||
var excludedProperties = new[]
|
||||
{
|
||||
"Parent", "Name", "Hostname", "Port", "Inheritance", "OpenConnections",
|
||||
"IsContainer", "IsDefault", "PositionID", "ConstantID", "TreeNode", "IsQuickConnect", "PleaseConnect"
|
||||
};
|
||||
public virtual IEnumerable<PropertyInfo> GetSerializableProperties()
|
||||
{
|
||||
var excludedProperties = new[] {
|
||||
nameof(Parent), nameof(Name), nameof(Hostname), nameof(Port),
|
||||
nameof(Username), nameof(Domain), nameof(Password),
|
||||
nameof(Inheritance), nameof(OpenConnections),
|
||||
nameof(IsContainer), nameof(IsDefault), nameof(ConstantID),
|
||||
nameof(IsQuickConnect), nameof(PleaseConnect), nameof(CredentialRecord)
|
||||
};
|
||||
|
||||
return GetProperties(excludedProperties);
|
||||
}
|
||||
return GetProperties(excludedProperties);
|
||||
}
|
||||
|
||||
public virtual void SetParent(ContainerInfo newParent)
|
||||
{
|
||||
public virtual void SetParent(ContainerInfo newParent)
|
||||
{
|
||||
RemoveParent();
|
||||
newParent?.AddChild(this);
|
||||
}
|
||||
newParent?.AddChild(this);
|
||||
}
|
||||
|
||||
public void RemoveParent()
|
||||
{
|
||||
|
||||
@@ -48,7 +48,13 @@ namespace mRemoteNG.Connection
|
||||
|
||||
#endregion
|
||||
|
||||
#region Connection
|
||||
#region
|
||||
|
||||
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 3),
|
||||
LocalizedAttributes.LocalizedDisplayNameInherit(nameof(Language.Credentials)),
|
||||
LocalizedAttributes.LocalizedDescriptionInherit(nameof(Language.PropertyDescriptionCredentials)),
|
||||
TypeConverter(typeof(MiscTools.YesNoTypeConverter))]
|
||||
public bool CredentialId { get; set; }
|
||||
|
||||
[LocalizedAttributes.LocalizedCategory(nameof(Language.Connection), 3),
|
||||
LocalizedAttributes.LocalizedDisplayNameInherit(nameof(Language.Username)),
|
||||
|
||||
@@ -8,6 +8,7 @@ using mRemoteNG.Messages;
|
||||
using mRemoteNG.Properties;
|
||||
using mRemoteNG.UI.Forms;
|
||||
using mRemoteNG.UI.Panels;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.UI.Tabs;
|
||||
using mRemoteNG.UI.Window;
|
||||
using WeifenLuo.WinFormsUI.Docking;
|
||||
@@ -19,6 +20,12 @@ namespace mRemoteNG.Connection
|
||||
{
|
||||
private readonly PanelAdder _panelAdder = new PanelAdder();
|
||||
private readonly List<string> _activeConnections = new List<string>();
|
||||
private readonly CredentialService _credentialService;
|
||||
|
||||
public ConnectionInitiator(CredentialService credentialService)
|
||||
{
|
||||
_credentialService = credentialService;
|
||||
}
|
||||
|
||||
public IEnumerable<string> ActiveConnections => _activeConnections;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using mRemoteNG.App;
|
||||
@@ -11,6 +13,7 @@ using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.Putty;
|
||||
using mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.Messages;
|
||||
using mRemoteNG.Properties;
|
||||
using mRemoteNG.Security;
|
||||
@@ -21,7 +24,7 @@ using mRemoteNG.UI;
|
||||
|
||||
namespace mRemoteNG.Connection
|
||||
{
|
||||
public class ConnectionsService
|
||||
public class ConnectionsService : IConnectionsService
|
||||
{
|
||||
private static readonly object SaveLock = new object();
|
||||
private readonly PuttySessionsManager _puttySessionsManager;
|
||||
@@ -30,6 +33,7 @@ namespace mRemoteNG.Connection
|
||||
private bool _batchingSaves = false;
|
||||
private bool _saveRequested = false;
|
||||
private bool _saveAsyncRequested = false;
|
||||
private readonly ICredentialService _credentialService;
|
||||
|
||||
public bool IsConnectionsFileLoaded { get; set; }
|
||||
public bool UsingDatabase { get; private set; }
|
||||
@@ -37,18 +41,18 @@ namespace mRemoteNG.Connection
|
||||
public RemoteConnectionsSyncronizer RemoteConnectionsSyncronizer { get; set; }
|
||||
public DateTime LastSqlUpdate { get; set; }
|
||||
|
||||
public ConnectionTreeModel ConnectionTreeModel { get; private set; }
|
||||
public IConnectionTreeModel ConnectionTreeModel { get; private set; } = new ConnectionTreeModel();
|
||||
|
||||
public ConnectionsService(PuttySessionsManager puttySessionsManager)
|
||||
public ConnectionsService(PuttySessionsManager puttySessionsManager, ICredentialService credentialService)
|
||||
{
|
||||
if (puttySessionsManager == null)
|
||||
throw new ArgumentNullException(nameof(puttySessionsManager));
|
||||
|
||||
_puttySessionsManager = puttySessionsManager;
|
||||
_puttySessionsManager = puttySessionsManager.ThrowIfNull(nameof(puttySessionsManager));
|
||||
_credentialService = credentialService.ThrowIfNull(nameof(credentialService));
|
||||
var path = SettingsFileInfo.SettingsPath;
|
||||
_localConnectionPropertiesDataProvider =
|
||||
new FileDataProvider(Path.Combine(path, "LocalConnectionProperties.xml"));
|
||||
_localConnectionPropertiesSerializer = new LocalConnectionPropertiesXmlSerializer();
|
||||
|
||||
_puttySessionsManager.RootPuttySessionsNodes.ForEach(node => ConnectionTreeModel.AddRootNode(node));
|
||||
}
|
||||
|
||||
public void NewConnectionsFile(string filename)
|
||||
@@ -56,10 +60,9 @@ namespace mRemoteNG.Connection
|
||||
try
|
||||
{
|
||||
filename.ThrowIfNullOrEmpty(nameof(filename));
|
||||
var newConnectionsModel = new ConnectionTreeModel();
|
||||
newConnectionsModel.AddRootNode(new RootNodeInfo(RootNodeType.Connection));
|
||||
SaveConnections(newConnectionsModel, false, new SaveFilter(), filename, true);
|
||||
LoadConnections(false, false, filename);
|
||||
ConnectionTreeModel.AddRootNode(new RootNodeInfo(RootNodeType.Connection));
|
||||
SaveConnections(ConnectionTreeModel, false, new SaveFilter(), filename, true);
|
||||
LoadConnections(false, filename);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -123,22 +126,21 @@ namespace mRemoteNG.Connection
|
||||
/// <param name="useDatabase"></param>
|
||||
/// <param name="import"></param>
|
||||
/// <param name="connectionFileName"></param>
|
||||
public void LoadConnections(bool useDatabase, bool import, string connectionFileName)
|
||||
public void LoadConnections(bool useDatabase, string connectionFileName)
|
||||
{
|
||||
var oldConnectionTreeModel = ConnectionTreeModel;
|
||||
var oldIsUsingDatabaseValue = UsingDatabase;
|
||||
|
||||
var connectionLoader = useDatabase
|
||||
? (IConnectionsLoader)new SqlConnectionsLoader(_localConnectionPropertiesSerializer,
|
||||
_localConnectionPropertiesDataProvider)
|
||||
: new XmlConnectionsLoader(connectionFileName);
|
||||
: new XmlConnectionsLoader(connectionFileName, _credentialService, this);
|
||||
|
||||
var newConnectionTreeModel = connectionLoader.Load();
|
||||
var serializationResult = connectionLoader.Load();
|
||||
|
||||
if (useDatabase)
|
||||
LastSqlUpdate = DateTime.Now;
|
||||
|
||||
if (newConnectionTreeModel == null)
|
||||
if (serializationResult == null)
|
||||
{
|
||||
DialogFactory.ShowLoadConnectionsFailedDialog(connectionFileName, "Decrypting connection file failed",
|
||||
IsConnectionsFileLoaded);
|
||||
@@ -149,16 +151,17 @@ namespace mRemoteNG.Connection
|
||||
ConnectionFileName = connectionFileName;
|
||||
UsingDatabase = useDatabase;
|
||||
|
||||
if (!import)
|
||||
{
|
||||
_puttySessionsManager.AddSessions();
|
||||
newConnectionTreeModel.RootNodes.AddRange(_puttySessionsManager.RootPuttySessionsNodes);
|
||||
}
|
||||
if (ConnectionTreeModel.RootNodes.Any())
|
||||
ConnectionTreeModel.RemoveRootNode(ConnectionTreeModel.RootNodes.First());
|
||||
|
||||
var rootNode = new RootNodeInfo(RootNodeType.Connection);
|
||||
rootNode.AddChildRange(serializationResult.ConnectionRecords);
|
||||
ConnectionTreeModel.AddRootNode(rootNode);
|
||||
|
||||
ConnectionTreeModel = newConnectionTreeModel;
|
||||
UpdateCustomConsPathSetting(connectionFileName);
|
||||
RaiseConnectionsLoadedEvent(oldConnectionTreeModel, newConnectionTreeModel, oldIsUsingDatabaseValue,
|
||||
useDatabase, connectionFileName);
|
||||
// TODO: fix this call
|
||||
RaiseConnectionsLoadedEvent(new List<ConnectionInfo>(), new List<ConnectionInfo>(),
|
||||
oldIsUsingDatabaseValue, useDatabase, connectionFileName);
|
||||
Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg,
|
||||
$"Connections loaded using {connectionLoader.GetType().Name}");
|
||||
}
|
||||
@@ -224,12 +227,13 @@ namespace mRemoteNG.Connection
|
||||
/// Optional. The name of the property that triggered
|
||||
/// this save.
|
||||
/// </param>
|
||||
public void SaveConnections(ConnectionTreeModel connectionTreeModel,
|
||||
bool useDatabase,
|
||||
SaveFilter saveFilter,
|
||||
string connectionFileName,
|
||||
bool forceSave = false,
|
||||
string propertyNameTrigger = "")
|
||||
public void SaveConnections(
|
||||
IConnectionTreeModel connectionTreeModel,
|
||||
bool useDatabase,
|
||||
SaveFilter saveFilter,
|
||||
string connectionFileName,
|
||||
bool forceSave = false,
|
||||
string propertyNameTrigger = "")
|
||||
{
|
||||
if (connectionTreeModel == null)
|
||||
return;
|
||||
@@ -251,9 +255,8 @@ namespace mRemoteNG.Connection
|
||||
var previouslyUsingDatabase = UsingDatabase;
|
||||
|
||||
var saver = useDatabase
|
||||
? (ISaver<ConnectionTreeModel>)new SqlConnectionsSaver(saveFilter,
|
||||
_localConnectionPropertiesSerializer,
|
||||
_localConnectionPropertiesDataProvider)
|
||||
? (ISaver<IConnectionTreeModel>)new SqlConnectionsSaver(saveFilter, _localConnectionPropertiesSerializer,
|
||||
_localConnectionPropertiesDataProvider)
|
||||
: new XmlConnectionsSaver(connectionFileName, saveFilter);
|
||||
|
||||
saver.Save(connectionTreeModel, propertyNameTrigger);
|
||||
@@ -356,31 +359,22 @@ namespace mRemoteNG.Connection
|
||||
public event EventHandler<ConnectionsLoadedEventArgs> ConnectionsLoaded;
|
||||
public event EventHandler<ConnectionsSavedEventArgs> ConnectionsSaved;
|
||||
|
||||
private void RaiseConnectionsLoadedEvent(Optional<ConnectionTreeModel> previousTreeModel,
|
||||
ConnectionTreeModel newTreeModel,
|
||||
bool previousSourceWasDatabase,
|
||||
bool newSourceIsDatabase,
|
||||
string newSourcePath)
|
||||
private void RaiseConnectionsLoadedEvent(List<ConnectionInfo> removedConnections, List<ConnectionInfo> addedConnections,
|
||||
bool previousSourceWasDatabase, bool newSourceIsDatabase,
|
||||
string newSourcePath)
|
||||
{
|
||||
ConnectionsLoaded?.Invoke(this, new ConnectionsLoadedEventArgs(
|
||||
previousTreeModel,
|
||||
newTreeModel,
|
||||
removedConnections,
|
||||
addedConnections,
|
||||
previousSourceWasDatabase,
|
||||
newSourceIsDatabase,
|
||||
newSourcePath));
|
||||
}
|
||||
|
||||
private void RaiseConnectionsSavedEvent(ConnectionTreeModel modelThatWasSaved,
|
||||
bool previouslyUsingDatabase,
|
||||
bool usingDatabase,
|
||||
string connectionFileName)
|
||||
private void RaiseConnectionsSavedEvent(IConnectionTreeModel modelThatWasSaved, bool previouslyUsingDatabase, bool usingDatabase, string connectionFileName)
|
||||
{
|
||||
ConnectionsSaved?.Invoke(this,
|
||||
new ConnectionsSavedEventArgs(modelThatWasSaved, previouslyUsingDatabase,
|
||||
usingDatabase,
|
||||
connectionFileName));
|
||||
ConnectionsSaved?.Invoke(this, new ConnectionsSavedEventArgs(modelThatWasSaved, previouslyUsingDatabase, usingDatabase, connectionFileName));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -67,8 +67,11 @@ namespace mRemoteNG.Connection
|
||||
throw new SettingsPropertyNotFoundException($"No property with name '{expectedPropertyName}' found.");
|
||||
|
||||
// ensure value is of correct type
|
||||
var value = Convert.ChangeType(property.GetValue(Instance, null),
|
||||
propertyFromDestination.PropertyType);
|
||||
var value = property.PropertyType == propertyFromDestination.PropertyType
|
||||
? property.GetValue(Instance, null)
|
||||
: propertyFromDestination.PropertyType == typeof(string)
|
||||
? property.GetValue(Instance, null).ToString()
|
||||
: Convert.ChangeType(property.GetValue(Instance, null), propertyFromDestination.PropertyType);
|
||||
|
||||
propertyFromDestination.SetValue(destinationInstance, value, null);
|
||||
}
|
||||
|
||||
78
mRemoteNG/Connection/IConnectionsService.cs
Normal file
78
mRemoteNG/Connection/IConnectionsService.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using mRemoteNG.Config.Connections;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tree;
|
||||
|
||||
namespace mRemoteNG.Connection
|
||||
{
|
||||
public interface IConnectionsService
|
||||
{
|
||||
IConnectionTreeModel ConnectionTreeModel { get; }
|
||||
void NewConnectionsFile(string filename);
|
||||
ConnectionInfo CreateQuickConnect(string connectionString, ProtocolType protocol);
|
||||
|
||||
/// <summary>
|
||||
/// Load connections from a source. <see cref="connectionFileName"/> is ignored if
|
||||
/// <see cref="useDatabase"/> is true.
|
||||
/// </summary>
|
||||
/// <param name="useDatabase"></param>
|
||||
/// <param name="import"></param>
|
||||
/// <param name="connectionFileName"></param>
|
||||
void LoadConnections(bool useDatabase, string connectionFileName);
|
||||
|
||||
/// <summary>
|
||||
/// When turned on, calls to <see cref="ConnectionsService.SaveConnections()"/> or
|
||||
/// <see cref="ConnectionsService.SaveConnectionsAsync"/> will not immediately execute.
|
||||
/// Instead, they will be deferred until <see cref="ConnectionsService.EndBatchingSaves"/>
|
||||
/// is called.
|
||||
/// </summary>
|
||||
void BeginBatchingSaves();
|
||||
|
||||
/// <summary>
|
||||
/// Immediately executes a single <see cref="ConnectionsService.SaveConnections()"/> or
|
||||
/// <see cref="ConnectionsService.SaveConnectionsAsync"/> if one has been requested
|
||||
/// since calling <see cref="ConnectionsService.BeginBatchingSaves"/>.
|
||||
/// </summary>
|
||||
void EndBatchingSaves();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the currently loaded <see cref="ConnectionsService.ConnectionTreeModel"/> with
|
||||
/// no <see cref="SaveFilter"/>.
|
||||
/// </summary>
|
||||
void SaveConnections();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the given <see cref="ConnectionsService.ConnectionTreeModel"/>.
|
||||
/// If <see cref="useDatabase"/> is true, <see cref="connectionFileName"/> is ignored
|
||||
/// </summary>
|
||||
/// <param name="connectionTreeModel"></param>
|
||||
/// <param name="useDatabase"></param>
|
||||
/// <param name="saveFilter"></param>
|
||||
/// <param name="connectionFileName"></param>
|
||||
/// <param name="forceSave">Bypasses safety checks that prevent saving if a connection file isn't loaded.</param>
|
||||
/// <param name="propertyNameTrigger">
|
||||
/// Optional. The name of the property that triggered
|
||||
/// this save.
|
||||
/// </param>
|
||||
void SaveConnections(
|
||||
IConnectionTreeModel connectionTreeModel,
|
||||
bool useDatabase,
|
||||
SaveFilter saveFilter,
|
||||
string connectionFileName,
|
||||
bool forceSave = false,
|
||||
string propertyNameTrigger = "");
|
||||
|
||||
/// <summary>
|
||||
/// Save the currently loaded connections asynchronously
|
||||
/// </summary>
|
||||
/// <param name="propertyNameTrigger">
|
||||
/// Optional. The name of the property that triggered
|
||||
/// this save.
|
||||
/// </param>
|
||||
void SaveConnectionsAsync(string propertyNameTrigger = "");
|
||||
|
||||
event EventHandler<ConnectionsLoadedEventArgs> ConnectionsLoaded;
|
||||
event EventHandler<ConnectionsSavedEventArgs> ConnectionsSaved;
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ namespace mRemoteNG.Connection.Protocol
|
||||
return false;
|
||||
}
|
||||
|
||||
var argParser = new ExternalToolArgumentParser(_externalTool.ConnectionInfo);
|
||||
var argParser = new ExternalToolArgumentParser(_externalTool.ConnectionInfo, Runtime.CredentialService);
|
||||
_process = new Process
|
||||
{
|
||||
StartInfo =
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Messages;
|
||||
using mRemoteNG.Security.SymmetricEncryption;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tools.Cmdline;
|
||||
using mRemoteNG.UI;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using mRemoteNG.Properties;
|
||||
using mRemoteNG.Security;
|
||||
|
||||
// ReSharper disable ArrangeAccessorOwnerBody
|
||||
|
||||
@@ -78,45 +78,20 @@ namespace mRemoteNG.Connection.Protocol
|
||||
|
||||
if (PuttyProtocol == Putty_Protocol.ssh)
|
||||
{
|
||||
var username = "";
|
||||
var password = "";
|
||||
|
||||
if (!string.IsNullOrEmpty(InterfaceControl.Info?.Username))
|
||||
{
|
||||
username = InterfaceControl.Info.Username;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||
switch (Settings.Default.EmptyCredentials)
|
||||
{
|
||||
case "windows":
|
||||
username = Environment.UserName;
|
||||
break;
|
||||
case "custom":
|
||||
username = Settings.Default.DefaultUsername;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(InterfaceControl.Info?.Password))
|
||||
{
|
||||
password = InterfaceControl.Info.Password;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Settings.Default.EmptyCredentials == "custom")
|
||||
{
|
||||
var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
|
||||
password = cryptographyProvider.Decrypt(Settings.Default.DefaultPassword,
|
||||
Runtime.EncryptionKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
arguments.Add("-" + (int)PuttySSHVersion);
|
||||
|
||||
|
||||
if (!Force.HasFlag(ConnectionInfo.Force.NoCredentials))
|
||||
{
|
||||
var cred = Runtime.CredentialService.GetEffectiveCredentialRecord(InterfaceControl?.Info.CredentialRecordId
|
||||
.FirstOrDefault()).FirstOrDefault();
|
||||
|
||||
var username = cred.Username;
|
||||
var password = cred.Password.ConvertToUnsecureString();
|
||||
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
arguments.Add("-l", username);
|
||||
@@ -149,11 +124,11 @@ namespace mRemoteNG.Connection.Protocol
|
||||
PuttyProcess.Exited += ProcessExited;
|
||||
|
||||
PuttyProcess.Start();
|
||||
PuttyProcess.WaitForInputIdle(Settings.Default.MaxPuttyWaitTime * 1000);
|
||||
PuttyProcess.WaitForInputIdle(Properties.Settings.Default.MaxPuttyWaitTime * 1000);
|
||||
|
||||
var startTicks = Environment.TickCount;
|
||||
while (PuttyHandle.ToInt32() == 0 &
|
||||
Environment.TickCount < startTicks + Settings.Default.MaxPuttyWaitTime * 1000)
|
||||
Environment.TickCount < startTicks + Properties.Settings.Default.MaxPuttyWaitTime * 1000)
|
||||
{
|
||||
if (_isPuttyNg)
|
||||
{
|
||||
|
||||
@@ -8,12 +8,13 @@ using AxMSTSCLib;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Messages;
|
||||
using mRemoteNG.Properties;
|
||||
using mRemoteNG.Security.SymmetricEncryption;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.UI;
|
||||
using mRemoteNG.UI.Forms;
|
||||
using mRemoteNG.UI.Tabs;
|
||||
using MSTSCLib;
|
||||
using System.Linq;
|
||||
using mRemoteNG.Security;
|
||||
|
||||
namespace mRemoteNG.Connection.Protocol.RDP
|
||||
{
|
||||
@@ -378,9 +379,9 @@ namespace mRemoteNG.Connection.Protocol.RDP
|
||||
{
|
||||
if (connectionInfo.RDGatewayUseConnectionCredentials == RDGatewayUseConnectionCredentials.Yes)
|
||||
{
|
||||
_rdpClient.TransportSettings2.GatewayUsername = connectionInfo.Username;
|
||||
_rdpClient.TransportSettings2.GatewayPassword = connectionInfo.Password;
|
||||
_rdpClient.TransportSettings2.GatewayDomain = connectionInfo?.Domain;
|
||||
_rdpClient.TransportSettings2.GatewayUsername = connectionInfo.CredentialRecord.Username;
|
||||
_rdpClient.TransportSettings2.GatewayPassword = connectionInfo.CredentialRecord.Password.ConvertToUnsecureString();
|
||||
_rdpClient.TransportSettings2.GatewayDomain = connectionInfo.CredentialRecord.Domain;
|
||||
}
|
||||
else if (connectionInfo.RDGatewayUseConnectionCredentials ==
|
||||
RDGatewayUseConnectionCredentials.SmartCard)
|
||||
@@ -456,58 +457,15 @@ namespace mRemoteNG.Connection.Protocol.RDP
|
||||
return;
|
||||
}
|
||||
|
||||
var userName = connectionInfo?.Username ?? "";
|
||||
var password = connectionInfo?.Password ?? "";
|
||||
var domain = connectionInfo?.Domain ?? "";
|
||||
var cred = Runtime.CredentialService.GetEffectiveCredentialRecord(connectionInfo.CredentialRecordId
|
||||
|
||||
.FirstOrDefault()).FirstOrDefault();
|
||||
|
||||
if (string.IsNullOrEmpty(userName))
|
||||
{
|
||||
if (Settings.Default.EmptyCredentials == "windows")
|
||||
{
|
||||
_rdpClient.UserName = Environment.UserName;
|
||||
}
|
||||
else if (Settings.Default.EmptyCredentials == "custom")
|
||||
{
|
||||
_rdpClient.UserName = Settings.Default.DefaultUsername;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_rdpClient.UserName = userName;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
if (Settings.Default.EmptyCredentials == "custom")
|
||||
{
|
||||
if (Settings.Default.DefaultPassword != "")
|
||||
{
|
||||
var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
|
||||
_rdpClient.AdvancedSettings2.ClearTextPassword =
|
||||
cryptographyProvider.Decrypt(Settings.Default.DefaultPassword, Runtime.EncryptionKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_rdpClient.AdvancedSettings2.ClearTextPassword = password;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(domain))
|
||||
{
|
||||
if (Settings.Default.EmptyCredentials == "windows")
|
||||
{
|
||||
_rdpClient.Domain = Environment.UserDomainName;
|
||||
}
|
||||
else if (Settings.Default.EmptyCredentials == "custom")
|
||||
{
|
||||
_rdpClient.Domain = Settings.Default.DefaultDomain;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_rdpClient.Domain = domain;
|
||||
}
|
||||
_rdpClient.UserName = cred.Username ?? "";
|
||||
|
||||
_rdpClient.AdvancedSettings2.ClearTextPassword = cred.Password?.ConvertToUnsecureString() ?? "";
|
||||
|
||||
_rdpClient.Domain = cred.Domain ?? "";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -816,4 +774,4 @@ namespace mRemoteNG.Connection.Protocol.RDP
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Threading;
|
||||
using System.ComponentModel;
|
||||
using System.Net.Sockets;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.UI.Forms;
|
||||
|
||||
@@ -13,6 +14,8 @@ namespace mRemoteNG.Connection.Protocol.VNC
|
||||
{
|
||||
public class ProtocolVNC : ProtocolBase, ISupportsViewOnly
|
||||
{
|
||||
private ConnectionInfo Info;
|
||||
|
||||
#region Properties
|
||||
|
||||
public bool SmartSize
|
||||
@@ -188,7 +191,7 @@ namespace mRemoteNG.Connection.Protocol.VNC
|
||||
_vnc.ConnectComplete += VNCEvent_Connected;
|
||||
_vnc.ConnectionLost += VNCEvent_Disconnected;
|
||||
FrmMain.ClipboardChanged += VNCEvent_ClipboardChanged;
|
||||
if (!Force.HasFlag(ConnectionInfo.Force.NoCredentials) && _info?.Password?.Length > 0)
|
||||
if (!Force.HasFlag(ConnectionInfo.Force.NoCredentials) && Info?.CredentialRecord?.Password?.Length > 0)
|
||||
{
|
||||
_vnc.GetPassword = VNCEvent_Authenticate;
|
||||
}
|
||||
@@ -268,7 +271,7 @@ namespace mRemoteNG.Connection.Protocol.VNC
|
||||
|
||||
private string VNCEvent_Authenticate()
|
||||
{
|
||||
return _info.Password;
|
||||
return Info.CredentialRecord.Password.ConvertToUnsecureString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Messages;
|
||||
using mRemoteNG.Tools;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.Root;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
|
||||
namespace mRemoteNG.Connection
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
|
||||
namespace mRemoteNG.Connection
|
||||
{
|
||||
@@ -16,7 +17,7 @@ namespace mRemoteNG.Connection
|
||||
if (string.IsNullOrEmpty(connectionInfo.Panel))
|
||||
connectionInfo.Panel = Language.General;
|
||||
connectionInfo.IsQuickConnect = true;
|
||||
var connectionInitiator = new ConnectionInitiator();
|
||||
var connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
|
||||
connectionInitiator.OpenConnection(connectionInfo, ConnectionInfo.Force.DoNotJump);
|
||||
}
|
||||
}
|
||||
|
||||
31
mRemoteNG/Credential/CredentialDomainUserPasswordComparer.cs
Normal file
31
mRemoteNG/Credential/CredentialDomainUserPasswordComparer.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using mRemoteNG.Security;
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
{
|
||||
public class CredentialDomainUserPasswordComparer : IEqualityComparer<ICredentialRecord>
|
||||
{
|
||||
public bool Equals(ICredentialRecord x, ICredentialRecord y)
|
||||
{
|
||||
if (x == null && y == null)
|
||||
return true;
|
||||
if (x == null || y == null)
|
||||
return false;
|
||||
|
||||
return GetHashCode(x) == GetHashCode(y);
|
||||
}
|
||||
|
||||
public int GetHashCode(ICredentialRecord obj)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 23 + obj.Username.GetHashCode();
|
||||
hash = hash * 23 + obj.Domain.GetHashCode();
|
||||
hash = hash * 23 + obj.Password.ConvertToUnsecureString().GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace mRemoteNG.Credential
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (!(value is Guid)) return base.ConvertFrom(context, culture, value);
|
||||
var matchedCredentials = Runtime.CredentialProviderCatalog.GetCredentialRecords()
|
||||
var matchedCredentials = Runtime.CredentialService.RepositoryList.GetCredentialRecords()
|
||||
.Where(record => record.Id.Equals(value)).ToArray();
|
||||
return matchedCredentials.Any() ? matchedCredentials.First() : null;
|
||||
}
|
||||
|
||||
145
mRemoteNG/Credential/CredentialService.cs
Normal file
145
mRemoteNG/Credential/CredentialService.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using mRemoteNG.Config;
|
||||
using mRemoteNG.Credential.Repositories;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tools.CustomCollections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
{
|
||||
public class CredentialService : ICredentialService
|
||||
{
|
||||
private readonly List<ICredentialRepositoryFactory> _repositoryFactories;
|
||||
private readonly ILoader<IEnumerable<ICredentialRepository>> _loader;
|
||||
private readonly ISaver<IEnumerable<ICredentialRepository>> _saver;
|
||||
|
||||
public ICredentialRepositoryList RepositoryList { get; }
|
||||
|
||||
public IReadOnlyCollection<ICredentialRepositoryFactory> RepositoryFactories => _repositoryFactories;
|
||||
|
||||
public CredentialService(
|
||||
ICredentialRepositoryList repositoryList,
|
||||
List<ICredentialRepositoryFactory> repositoryFactories,
|
||||
ILoader<IEnumerable<ICredentialRepository>> loader,
|
||||
ISaver<IEnumerable<ICredentialRepository>> saver)
|
||||
{
|
||||
RepositoryList = repositoryList.ThrowIfNull(nameof(repositoryList));
|
||||
_repositoryFactories = repositoryFactories.ThrowIfNull(nameof(repositoryFactories));
|
||||
_loader = loader.ThrowIfNull(nameof(loader));
|
||||
_saver = saver.ThrowIfNull(nameof(saver));
|
||||
|
||||
SetupEventHandlers();
|
||||
}
|
||||
|
||||
public void SaveRepositoryList()
|
||||
{
|
||||
_saver.Save(RepositoryList);
|
||||
}
|
||||
|
||||
public void LoadRepositoryList()
|
||||
{
|
||||
var loadedRepositories = _loader.Load();
|
||||
|
||||
foreach (var repository in loadedRepositories)
|
||||
{
|
||||
RepositoryList.AddProvider(repository);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRepository(ICredentialRepository repository)
|
||||
{
|
||||
RepositoryList.AddProvider(repository);
|
||||
}
|
||||
|
||||
public void RemoveRepository(ICredentialRepository repository)
|
||||
{
|
||||
RepositoryList.RemoveProvider(repository);
|
||||
}
|
||||
|
||||
public IEnumerable<ICredentialRecord> GetCredentialRecords()
|
||||
{
|
||||
return RepositoryList.GetCredentialRecords();
|
||||
}
|
||||
|
||||
public ICredentialRecord GetCredentialRecord(Guid id)
|
||||
{
|
||||
return RepositoryList.GetCredentialRecord(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ICredentialRepository"/> object to use, taking into account
|
||||
/// any default or replacement credentials that may be used.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="allowDefaultFallback">
|
||||
/// If True and the <see cref="ICredentialRecord"/> given by <see cref="id"/> cannot be found,
|
||||
/// we will attempt to use a default credential specified in settings. If False, no default
|
||||
/// fallback value will be used.
|
||||
/// </param>
|
||||
public Optional<ICredentialRecord> GetEffectiveCredentialRecord(Optional<Guid> id, bool allowDefaultFallback = true)
|
||||
{
|
||||
var desiredCredentialRecord = GetCredentialRecord(id.FirstOrDefault());
|
||||
if (desiredCredentialRecord != null)
|
||||
return desiredCredentialRecord.ToOptional();
|
||||
|
||||
if (allowDefaultFallback)
|
||||
{
|
||||
if (Properties.Settings.Default.EmptyCredentials == "windows")
|
||||
return new WindowsDefaultCredentialRecord();
|
||||
|
||||
if (Properties.Settings.Default.EmptyCredentials == "custom")
|
||||
{
|
||||
var cred = GetCredentialRecord(Properties.Settings.Default.DefaultCredentialRecord);
|
||||
if (cred != null)
|
||||
return cred.ToOptional();
|
||||
}
|
||||
}
|
||||
|
||||
if (!id.Any() || id.FirstOrDefault() == Guid.Empty)
|
||||
return new NullCredentialRecord();
|
||||
|
||||
return new UnavailableCredentialRecord(id.First());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an <see cref="ICredentialRepositoryFactory"/> for
|
||||
/// use throughout the application.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
public void RegisterRepositoryFactory(ICredentialRepositoryFactory factory)
|
||||
{
|
||||
if (_repositoryFactories.Contains(factory))
|
||||
return;
|
||||
|
||||
_repositoryFactories.Add(factory);
|
||||
}
|
||||
|
||||
public Optional<ICredentialRepositoryFactory> GetRepositoryFactoryForConfig(ICredentialRepositoryConfig repositoryConfig)
|
||||
{
|
||||
return new Optional<ICredentialRepositoryFactory>(
|
||||
RepositoryFactories
|
||||
.FirstOrDefault(factory =>
|
||||
string.Equals(factory.SupportsConfigType, repositoryConfig.TypeName)));
|
||||
}
|
||||
|
||||
#region Setup
|
||||
private void SetupEventHandlers()
|
||||
{
|
||||
RepositoryList.RepositoriesUpdated += HandleRepositoriesUpdatedEvent;
|
||||
RepositoryList.CredentialsUpdated += HandleCredentialsUpdatedEvent;
|
||||
}
|
||||
|
||||
private void HandleRepositoriesUpdatedEvent(object sender, CollectionUpdatedEventArgs<ICredentialRepository> collectionUpdatedEventArgs)
|
||||
{
|
||||
SaveRepositoryList();
|
||||
}
|
||||
|
||||
private void HandleCredentialsUpdatedEvent(object sender, CollectionUpdatedEventArgs<ICredentialRecord> collectionUpdatedEventArgs)
|
||||
{
|
||||
var repo = sender as ICredentialRepository;
|
||||
repo?.SaveCredentials(repo.Config.Key);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,24 @@
|
||||
using System.IO;
|
||||
using mRemoteNG.App;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using mRemoteNG.App.Info;
|
||||
using mRemoteNG.Config;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.Serializers.CredentialProviderSerializer;
|
||||
using mRemoteNG.Config.Serializers.CredentialSerializer;
|
||||
using mRemoteNG.Security.Factories;
|
||||
using mRemoteNG.Credential.Repositories;
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
{
|
||||
public class CredentialServiceFactory
|
||||
{
|
||||
// When we get a true CompositionRoot we can move this to that class. We should only require 1 instance of this service at a time
|
||||
public CredentialServiceFacade Build()
|
||||
public CredentialService Build()
|
||||
{
|
||||
var cryptoFromSettings = new CryptoProviderFactoryFromSettings();
|
||||
var credRepoSerializer = new XmlCredentialPasswordEncryptorDecorator(
|
||||
cryptoFromSettings.Build(),
|
||||
new XmlCredentialRecordSerializer());
|
||||
var credRepoDeserializer =
|
||||
new XmlCredentialPasswordDecryptorDecorator(new XmlCredentialRecordDeserializer());
|
||||
|
||||
var repositoryList = new CredentialRepositoryList();
|
||||
var credentialRepoListPath = Path.Combine(SettingsFileInfo.SettingsPath, "credentialRepositories.xml");
|
||||
var repoListDataProvider = new FileDataProvider(credentialRepoListPath);
|
||||
var repoListLoader = new CredentialRepositoryListLoader(
|
||||
repoListDataProvider,
|
||||
new
|
||||
CredentialRepositoryListDeserializer(credRepoSerializer,
|
||||
credRepoDeserializer));
|
||||
var repoListSaver = new CredentialRepositoryListSaver(repoListDataProvider);
|
||||
var repositoryFactories = new List<ICredentialRepositoryFactory>();
|
||||
var persistor = new CredentialRepositoryListPersistor(repoListDataProvider, repositoryFactories);
|
||||
|
||||
return new CredentialServiceFacade(Runtime.CredentialProviderCatalog, repoListLoader, repoListSaver);
|
||||
return new CredentialService(repositoryList, repositoryFactories, persistor, persistor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,37 @@
|
||||
using System.ComponentModel;
|
||||
using System.Security;
|
||||
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a named set of username/domain/password information.
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(CredentialRecordTypeConverter))]
|
||||
public interface ICredentialRecord : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// An Id which uniquely identifies this credential record.
|
||||
/// </summary>
|
||||
Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A friendly name for this credential record.
|
||||
/// </summary>
|
||||
string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The username portion of the credential.
|
||||
/// </summary>
|
||||
string Username { get; set; }
|
||||
SecureString Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The domain portion of the credential.
|
||||
/// </summary>
|
||||
string Domain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The password
|
||||
/// </summary>
|
||||
SecureString Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,52 @@ namespace mRemoteNG.Credential
|
||||
{
|
||||
public interface ICredentialRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The configuration information for this credential repository.
|
||||
/// </summary>
|
||||
ICredentialRepositoryConfig Config { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of the <see cref="ICredentialRecord"/>s provided by this repository.
|
||||
/// </summary>
|
||||
IList<ICredentialRecord> CredentialRecords { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A friendly name for this repository.
|
||||
/// </summary>
|
||||
string Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this repository has been unlocked and is able to provide
|
||||
/// credentials.
|
||||
/// </summary>
|
||||
bool IsLoaded { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Unlock the repository with the given key and load all available credentials.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
void LoadCredentials(SecureString key);
|
||||
|
||||
/// <summary>
|
||||
/// Save all credentials provided by this repository.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
void SaveCredentials(SecureString key);
|
||||
|
||||
/// <summary>
|
||||
/// Lock and unload all credentials provided by this repository.
|
||||
/// </summary>
|
||||
void UnloadCredentials();
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised when any changes are made to the assigned <see cref="Config"/>.
|
||||
/// </summary>
|
||||
event EventHandler RepositoryConfigUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised when a credential is added or removed from this repository.
|
||||
/// </summary>
|
||||
event EventHandler<CollectionUpdatedEventArgs<ICredentialRecord>> CredentialsUpdated;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using mRemoteNG.Credential.Repositories;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tools.CustomCollections;
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
@@ -12,7 +14,9 @@ namespace mRemoteNG.Credential
|
||||
|
||||
void RemoveProvider(ICredentialRepository credentialProvider);
|
||||
|
||||
bool Contains(Guid repositoryId);
|
||||
Optional<ICredentialRepository> GetProvider(Guid id);
|
||||
|
||||
bool Contains(ICredentialRepositoryConfig repositoryConfig);
|
||||
|
||||
IEnumerable<ICredentialRecord> GetCredentialRecords();
|
||||
|
||||
|
||||
40
mRemoteNG/Credential/ICredentialService.cs
Normal file
40
mRemoteNG/Credential/ICredentialService.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using mRemoteNG.Credential.Repositories;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
{
|
||||
public interface ICredentialService
|
||||
{
|
||||
ICredentialRepositoryList RepositoryList { get; }
|
||||
IReadOnlyCollection<ICredentialRepositoryFactory> RepositoryFactories { get; }
|
||||
void SaveRepositoryList();
|
||||
void LoadRepositoryList();
|
||||
void AddRepository(ICredentialRepository repository);
|
||||
void RemoveRepository(ICredentialRepository repository);
|
||||
IEnumerable<ICredentialRecord> GetCredentialRecords();
|
||||
ICredentialRecord GetCredentialRecord(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ICredentialRepository"/> object to use, taking into account
|
||||
/// any default or replacement credentials that may be used.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="allowDefaultFallback">
|
||||
/// If True and the <see cref="ICredentialRecord"/> given by <see cref="id"/> cannot be found,
|
||||
/// we will attempt to use a default credential specified in settings. If False, no default
|
||||
/// fallback value will be used.
|
||||
/// </param>
|
||||
Optional<ICredentialRecord> GetEffectiveCredentialRecord(Optional<Guid> id, bool allowDefaultFallback = true);
|
||||
|
||||
/// <summary>
|
||||
/// Registers an <see cref="ICredentialRepositoryFactory"/> for
|
||||
/// use throughout the application.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
void RegisterRepositoryFactory(ICredentialRepositoryFactory factory);
|
||||
|
||||
Optional<ICredentialRepositoryFactory> GetRepositoryFactoryForConfig(ICredentialRepositoryConfig repositoryConfig);
|
||||
}
|
||||
}
|
||||
86
mRemoteNG/Credential/Records/CredentialRecord.cs
Normal file
86
mRemoteNG/Credential/Records/CredentialRecord.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Security;
|
||||
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
{
|
||||
public class CredentialRecord : ICredentialRecord
|
||||
{
|
||||
private string _title = "New Credential";
|
||||
private string _username = "";
|
||||
private SecureString _password = new SecureString();
|
||||
private string _domain = "";
|
||||
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
|
||||
public string Title
|
||||
{
|
||||
get { return _title; }
|
||||
set
|
||||
{
|
||||
_title = value;
|
||||
RaisePropertyChangedEvent(nameof(Title));
|
||||
}
|
||||
}
|
||||
|
||||
public string Username
|
||||
{
|
||||
get { return _username; }
|
||||
set
|
||||
{
|
||||
_username = value;
|
||||
RaisePropertyChangedEvent(nameof(Username));
|
||||
}
|
||||
}
|
||||
|
||||
public SecureString Password
|
||||
{
|
||||
get { return _password; }
|
||||
set
|
||||
{
|
||||
_password = value;
|
||||
RaisePropertyChangedEvent(nameof(Password));
|
||||
}
|
||||
}
|
||||
|
||||
public string Domain
|
||||
{
|
||||
get { return _domain; }
|
||||
set
|
||||
{
|
||||
_domain = value;
|
||||
RaisePropertyChangedEvent(nameof(Domain));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public CredentialRecord()
|
||||
{
|
||||
}
|
||||
|
||||
public CredentialRecord(ICredentialRecord otherCredential)
|
||||
{
|
||||
Username = otherCredential.Username;
|
||||
Password = otherCredential.Password;
|
||||
Domain = otherCredential.Domain;
|
||||
}
|
||||
|
||||
public CredentialRecord(Guid customGuid)
|
||||
{
|
||||
Id = customGuid;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Title;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChangedEvent(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
36
mRemoteNG/Credential/Records/NullCredentialRecord.cs
Normal file
36
mRemoteNG/Credential/Records/NullCredentialRecord.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
{
|
||||
public class NullCredentialRecord : ICredentialRecord
|
||||
{
|
||||
public Guid Id { get; } = Guid.Empty;
|
||||
|
||||
[ReadOnly(true)]
|
||||
public string Title { get; set; } = $"--{Language.None}--";
|
||||
|
||||
[ReadOnly(true)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[ReadOnly(true)]
|
||||
public SecureString Password { get; set; } = new SecureString();
|
||||
|
||||
[ReadOnly(true)]
|
||||
public string Domain { get; set; } = string.Empty;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Title;
|
||||
}
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
38
mRemoteNG/Credential/Records/UnavailableCredentialRecord.cs
Normal file
38
mRemoteNG/Credential/Records/UnavailableCredentialRecord.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
{
|
||||
public class UnavailableCredentialRecord : ICredentialRecord
|
||||
{
|
||||
public Guid Id { get; }
|
||||
|
||||
[ReadOnly(true)]
|
||||
public string Title { get; set; } = Language.CredentialUnavailable;
|
||||
|
||||
[ReadOnly(true)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[ReadOnly(true)]
|
||||
public SecureString Password { get; set; } = new SecureString();
|
||||
|
||||
[ReadOnly(true)]
|
||||
public string Domain { get; set; } = string.Empty;
|
||||
|
||||
public UnavailableCredentialRecord(Guid id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public override string ToString() => Language.CredentialUnavailable;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Security;
|
||||
|
||||
namespace mRemoteNG.Credential
|
||||
{
|
||||
public class WindowsDefaultCredentialRecord : ICredentialRecord
|
||||
{
|
||||
private readonly string _title;
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set {}
|
||||
}
|
||||
|
||||
public string Username
|
||||
{
|
||||
get => Environment.UserName;
|
||||
set {}
|
||||
}
|
||||
|
||||
public SecureString Password
|
||||
{
|
||||
get => new SecureString();
|
||||
set {}
|
||||
}
|
||||
|
||||
public string Domain
|
||||
{
|
||||
get => Environment.UserDomainName;
|
||||
set {}
|
||||
}
|
||||
|
||||
|
||||
public WindowsDefaultCredentialRecord()
|
||||
{
|
||||
// TODO: localizations
|
||||
_title = "Windows Default Credentials";
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Title;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected virtual void RaisePropertyChangedEvent(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.Credential.Repositories
|
||||
{
|
||||
@@ -10,7 +11,7 @@ namespace mRemoteNG.Credential.Repositories
|
||||
private readonly List<ICredentialRepository> _repositories = new List<ICredentialRepository>();
|
||||
|
||||
public IEnumerable<ICredentialRepository> Repositories => _repositories;
|
||||
public ICredentialRepository SelectedRepository { get; set; }
|
||||
public Optional<ICredentialRepository> SelectedRepository { get; set; } = Optional<ICredentialRepository>.Empty;
|
||||
|
||||
public CompositeRepositoryUnlocker(IEnumerable<ICredentialRepository> repositories)
|
||||
{
|
||||
@@ -23,7 +24,8 @@ namespace mRemoteNG.Credential.Repositories
|
||||
|
||||
public void Unlock(SecureString key)
|
||||
{
|
||||
SelectedRepository.LoadCredentials(key);
|
||||
if (SelectedRepository.Any())
|
||||
SelectedRepository.First().LoadCredentials(key);
|
||||
}
|
||||
|
||||
public void SelectNextLockedRepository()
|
||||
@@ -31,10 +33,10 @@ namespace mRemoteNG.Credential.Repositories
|
||||
SelectedRepository = GetNextLockedRepo();
|
||||
}
|
||||
|
||||
private ICredentialRepository GetNextLockedRepo()
|
||||
private Optional<ICredentialRepository> GetNextLockedRepo()
|
||||
{
|
||||
var newOrder = OrderListForNextLockedRepo();
|
||||
return newOrder.Any() ? newOrder.First() : null;
|
||||
return new Optional<ICredentialRepository>(newOrder.FirstOrDefault());
|
||||
}
|
||||
|
||||
private IList<ICredentialRepository> OrderListForNextLockedRepo()
|
||||
@@ -67,7 +69,10 @@ namespace mRemoteNG.Credential.Repositories
|
||||
|
||||
private int GetNewListStartIndex()
|
||||
{
|
||||
var currentItemIndex = _repositories.IndexOf(SelectedRepository);
|
||||
if (!SelectedRepository.Any())
|
||||
return 0;
|
||||
|
||||
var currentItemIndex = _repositories.IndexOf(SelectedRepository.First());
|
||||
return currentItemIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tools.CustomCollections;
|
||||
|
||||
namespace mRemoteNG.Credential.Repositories
|
||||
@@ -15,7 +16,9 @@ namespace mRemoteNG.Credential.Repositories
|
||||
|
||||
public void AddProvider(ICredentialRepository credentialProvider)
|
||||
{
|
||||
if (Contains(credentialProvider.Config.Id)) return;
|
||||
if (Contains(credentialProvider.Config))
|
||||
return;
|
||||
|
||||
_credentialProviders.Add(credentialProvider);
|
||||
credentialProvider.CredentialsUpdated += RaiseCredentialsUpdatedEvent;
|
||||
credentialProvider.RepositoryConfigUpdated += OnRepoConfigChanged;
|
||||
@@ -24,16 +27,27 @@ namespace mRemoteNG.Credential.Repositories
|
||||
|
||||
public void RemoveProvider(ICredentialRepository credentialProvider)
|
||||
{
|
||||
if (!Contains(credentialProvider.Config.Id)) return;
|
||||
if (!Contains(credentialProvider.Config))
|
||||
return;
|
||||
|
||||
credentialProvider.CredentialsUpdated -= RaiseCredentialsUpdatedEvent;
|
||||
credentialProvider.RepositoryConfigUpdated -= OnRepoConfigChanged;
|
||||
_credentialProviders.Remove(credentialProvider);
|
||||
RaiseRepositoriesUpdatedEvent(ActionType.Removed, new[] {credentialProvider});
|
||||
}
|
||||
|
||||
public bool Contains(Guid repositoryId)
|
||||
public Optional<ICredentialRepository> GetProvider(Guid id)
|
||||
{
|
||||
return _credentialProviders.Any(repo => repo.Config.Id == repositoryId);
|
||||
return _credentialProviders
|
||||
.FirstOrDefault(repo => repo.Config.Id.Equals(id))
|
||||
.ToOptional();
|
||||
}
|
||||
|
||||
public bool Contains(ICredentialRepositoryConfig repositoryConfig)
|
||||
{
|
||||
return _credentialProviders.Any(repo =>
|
||||
repo.Config.Id == repositoryConfig.Id ||
|
||||
string.Equals(repo.Config.Source, repositoryConfig.Source));
|
||||
}
|
||||
|
||||
public IEnumerable<ICredentialRecord> GetCredentialRecords()
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace mRemoteNG.Credential.Repositories
|
||||
{
|
||||
public class CredentialRepositoryTypeNotSupportedException : Exception
|
||||
{
|
||||
public CredentialRepositoryTypeNotSupportedException()
|
||||
{
|
||||
}
|
||||
|
||||
public CredentialRepositoryTypeNotSupportedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public CredentialRepositoryTypeNotSupportedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,33 @@ namespace mRemoteNG.Credential.Repositories
|
||||
{
|
||||
public interface ICredentialRepositoryConfig : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// An Id which uniquely identifies this credential repository.
|
||||
/// </summary>
|
||||
Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A friendly name for this credential repository
|
||||
/// </summary>
|
||||
string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A name which uniquely identifies the type of credential repository this
|
||||
/// config represents. This is used for determining which <see cref="ICredentialRepositoryFactory"/>
|
||||
/// to use to create the <see cref="ICredentialRepository"/>.
|
||||
/// </summary>
|
||||
string TypeName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A string representing how to access the persisted credential records.
|
||||
/// This may be a file path, URL, database connection string, etc. depending
|
||||
/// on the implementation of the <see cref="ICredentialRepository"/>.
|
||||
/// </summary>
|
||||
string Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The password necessary to unlock and access the underlying repository.
|
||||
/// </summary>
|
||||
SecureString Key { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace mRemoteNG.Credential.Repositories
|
||||
{
|
||||
public interface ICredentialRepositoryFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ICredentialRepositoryConfig.TypeName"/> that this factory can build.
|
||||
/// </summary>
|
||||
string SupportsConfigType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Builds a new <see cref="ICredentialRepositoryFactory"/> given the <see cref="ICredentialRepositoryConfig"/>
|
||||
/// that describes it. The <see cref="ICredentialRepositoryConfig.TypeName"/> must match this factory's
|
||||
/// <see cref="SupportsConfigType"/> property.
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="isLoaded"></param>
|
||||
/// <returns></returns>
|
||||
ICredentialRepository Build(ICredentialRepositoryConfig config, bool isLoaded = false);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using mRemoteNG.Config;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tools.CustomCollections;
|
||||
|
||||
namespace mRemoteNG.Credential.Repositories
|
||||
@@ -15,26 +16,36 @@ namespace mRemoteNG.Credential.Repositories
|
||||
|
||||
public ICredentialRepositoryConfig Config { get; }
|
||||
public IList<ICredentialRecord> CredentialRecords { get; }
|
||||
public string Title => Config.Title;
|
||||
public bool IsLoaded { get; private set; }
|
||||
|
||||
public XmlCredentialRepository(ICredentialRepositoryConfig config,
|
||||
CredentialRecordSaver credentialRecordSaver,
|
||||
CredentialRecordLoader credentialRecordLoader)
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="XmlCredentialRepository"/> instance,
|
||||
/// providing access to load and save credentials stored in an XML
|
||||
/// format.
|
||||
/// </summary>
|
||||
/// <param name="config">
|
||||
/// The config representing this repository
|
||||
/// </param>
|
||||
/// <param name="credentialRecordSaver"></param>
|
||||
/// <param name="credentialRecordLoader"></param>
|
||||
/// <param name="isLoaded">
|
||||
/// Does this instance represent a repository that is already loaded?
|
||||
/// </param>
|
||||
public XmlCredentialRepository(
|
||||
ICredentialRepositoryConfig config,
|
||||
CredentialRecordSaver credentialRecordSaver,
|
||||
CredentialRecordLoader credentialRecordLoader,
|
||||
bool isLoaded = false)
|
||||
{
|
||||
if (config == null)
|
||||
throw new ArgumentNullException(nameof(config));
|
||||
if (credentialRecordSaver == null)
|
||||
throw new ArgumentNullException(nameof(credentialRecordSaver));
|
||||
if (credentialRecordLoader == null)
|
||||
throw new ArgumentNullException(nameof(credentialRecordLoader));
|
||||
Config = config.ThrowIfNull(nameof(config));
|
||||
_credentialRecordSaver = credentialRecordSaver.ThrowIfNull(nameof(credentialRecordSaver));
|
||||
_credentialRecordLoader = credentialRecordLoader.ThrowIfNull(nameof(credentialRecordLoader));
|
||||
IsLoaded = isLoaded;
|
||||
|
||||
Config = config;
|
||||
CredentialRecords = new FullyObservableCollection<ICredentialRecord>();
|
||||
((FullyObservableCollection<ICredentialRecord>)CredentialRecords).CollectionUpdated +=
|
||||
RaiseCredentialsUpdatedEvent;
|
||||
((FullyObservableCollection<ICredentialRecord>) CredentialRecords).CollectionUpdated += RaiseCredentialsUpdatedEvent;
|
||||
Config.PropertyChanged += (sender, args) => RaiseRepositoryConfigUpdatedEvent(args);
|
||||
_credentialRecordSaver = credentialRecordSaver;
|
||||
_credentialRecordLoader = credentialRecordLoader;
|
||||
}
|
||||
|
||||
public void LoadCredentials(SecureString key)
|
||||
@@ -42,7 +53,9 @@ namespace mRemoteNG.Credential.Repositories
|
||||
var credentials = _credentialRecordLoader.Load(key);
|
||||
foreach (var newCredential in credentials)
|
||||
{
|
||||
if (ThisIsADuplicateCredentialRecord(newCredential)) continue;
|
||||
if (ThisIsADuplicateCredentialRecord(newCredential))
|
||||
continue;
|
||||
|
||||
CredentialRecords.Add(newCredential);
|
||||
}
|
||||
|
||||
@@ -63,7 +76,9 @@ namespace mRemoteNG.Credential.Repositories
|
||||
|
||||
public void SaveCredentials(SecureString key)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
_credentialRecordSaver.Save(CredentialRecords, key);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Generic;
|
||||
using mRemoteNG.Config;
|
||||
using mRemoteNG.Config.DataProviders;
|
||||
using mRemoteNG.Config.Serializers;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.Credential.Repositories
|
||||
{
|
||||
public class XmlCredentialRepositoryFactory
|
||||
public class XmlCredentialRepositoryFactory : ICredentialRepositoryFactory
|
||||
{
|
||||
private readonly ISecureSerializer<IEnumerable<ICredentialRecord>, string> _serializer;
|
||||
private readonly ISecureDeserializer<string, IEnumerable<ICredentialRecord>> _deserializer;
|
||||
|
||||
public XmlCredentialRepositoryFactory(ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer,
|
||||
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer)
|
||||
public XmlCredentialRepositoryFactory(
|
||||
ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer,
|
||||
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer)
|
||||
{
|
||||
if (serializer == null)
|
||||
throw new ArgumentNullException(nameof(serializer));
|
||||
if (deserializer == null)
|
||||
throw new ArgumentNullException(nameof(deserializer));
|
||||
|
||||
_serializer = serializer;
|
||||
_deserializer = deserializer;
|
||||
_serializer = serializer.ThrowIfNull(nameof(serializer));
|
||||
_deserializer = deserializer.ThrowIfNull(nameof(deserializer));
|
||||
}
|
||||
|
||||
public ICredentialRepository Build(ICredentialRepositoryConfig config)
|
||||
{
|
||||
return BuildXmlRepo(config);
|
||||
}
|
||||
public string SupportsConfigType { get; } = "Xml";
|
||||
|
||||
public ICredentialRepository Build(XElement repositoryXElement)
|
||||
{
|
||||
var stringId = repositoryXElement.Attribute("Id")?.Value;
|
||||
Guid id;
|
||||
Guid.TryParse(stringId, out id);
|
||||
if (id.Equals(Guid.Empty)) id = Guid.NewGuid();
|
||||
var config = new CredentialRepositoryConfig(id)
|
||||
{
|
||||
TypeName = repositoryXElement.Attribute("TypeName")?.Value,
|
||||
Title = repositoryXElement.Attribute("Title")?.Value,
|
||||
Source = repositoryXElement.Attribute("Source")?.Value
|
||||
};
|
||||
return BuildXmlRepo(config);
|
||||
}
|
||||
|
||||
private ICredentialRepository BuildXmlRepo(ICredentialRepositoryConfig config)
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="XmlCredentialRepository"/> instance for
|
||||
/// the given <see cref="ICredentialRepositoryConfig"/>.
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="isLoaded">
|
||||
/// Does this instance represent a repository that is already loaded?
|
||||
/// </param>
|
||||
public ICredentialRepository Build(ICredentialRepositoryConfig config, bool isLoaded = false)
|
||||
{
|
||||
var dataProvider = new FileDataProvider(config.Source);
|
||||
var saver = new CredentialRecordSaver(dataProvider, _serializer);
|
||||
var loader = new CredentialRecordLoader(dataProvider, _deserializer);
|
||||
return new XmlCredentialRepository(config, saver, loader);
|
||||
|
||||
return new XmlCredentialRepository(config, saver, loader, isLoaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
mRemoteNG/Language/Language.Designer.cs
generated
45
mRemoteNG/Language/Language.Designer.cs
generated
@@ -510,6 +510,15 @@ namespace mRemoteNG {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die Back ähnelt.
|
||||
/// </summary>
|
||||
internal static string Back {
|
||||
get {
|
||||
return ResourceManager.GetString("Back", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die Default Inheritance ähnelt.
|
||||
/// </summary>
|
||||
@@ -1258,6 +1267,15 @@ namespace mRemoteNG {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die Continue ähnelt.
|
||||
/// </summary>
|
||||
internal static string Continue {
|
||||
get {
|
||||
return ResourceManager.GetString("Continue", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die Copy ähnelt.
|
||||
/// </summary>
|
||||
@@ -3459,6 +3477,15 @@ namespace mRemoteNG {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die TODO ähnelt.
|
||||
/// </summary>
|
||||
internal static string PropertyDescriptionCredentials {
|
||||
get {
|
||||
return ResourceManager.GetString("PropertyDescriptionCredentials", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die Put your notes or a description for the host here. ähnelt.
|
||||
/// </summary>
|
||||
@@ -4893,6 +4920,15 @@ namespace mRemoteNG {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die Lock ähnelt.
|
||||
/// </summary>
|
||||
internal static string RepoLock {
|
||||
get {
|
||||
return ResourceManager.GetString("RepoLock", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die Report a Bug ähnelt.
|
||||
/// </summary>
|
||||
@@ -4902,6 +4938,15 @@ namespace mRemoteNG {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die Unlock ähnelt.
|
||||
/// </summary>
|
||||
internal static string RepoUnlock {
|
||||
get {
|
||||
return ResourceManager.GetString("RepoUnlock", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Zeichenfolge, die Reset layout ähnelt.
|
||||
/// </summary>
|
||||
|
||||
@@ -2153,4 +2153,19 @@ Nightly Channel includes Alphas, Betas & Release Candidates.</value>
|
||||
<data name="SmartCard" xml:space="preserve">
|
||||
<value>SmartCard</value>
|
||||
</data>
|
||||
<data name="PropertyDescriptionCredentials" xml:space="preserve">
|
||||
<value>TODO</value>
|
||||
</data>
|
||||
<data name="RepoLock" xml:space="preserve">
|
||||
<value>Lock</value>
|
||||
</data>
|
||||
<data name="RepoUnlock" xml:space="preserve">
|
||||
<value>Unlock</value>
|
||||
</data>
|
||||
<data name="Back" xml:space="preserve">
|
||||
<value>Back</value>
|
||||
</data>
|
||||
<data name="Continue" xml:space="preserve">
|
||||
<value>Continue</value>
|
||||
</data>
|
||||
</root>
|
||||
20
mRemoteNG/Properties/Resources.Designer.cs
generated
20
mRemoteNG/Properties/Resources.Designer.cs
generated
@@ -200,6 +200,16 @@ namespace mRemoteNG.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap Edit_16x {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("Edit_16x", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
@@ -766,6 +776,16 @@ namespace mRemoteNG.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap Unlock_16x {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("Unlock_16x", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
|
||||
@@ -337,4 +337,10 @@
|
||||
<data name="Property_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Property_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Edit_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Edit_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Unlock_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Unlock_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
</root>
|
||||
60
mRemoteNG/Properties/Settings.Designer.cs
generated
60
mRemoteNG/Properties/Settings.Designer.cs
generated
@@ -251,42 +251,6 @@ namespace mRemoteNG.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string DefaultUsername {
|
||||
get {
|
||||
return ((string)(this["DefaultUsername"]));
|
||||
}
|
||||
set {
|
||||
this["DefaultUsername"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string DefaultPassword {
|
||||
get {
|
||||
return ((string)(this["DefaultPassword"]));
|
||||
}
|
||||
set {
|
||||
this["DefaultPassword"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string DefaultDomain {
|
||||
get {
|
||||
return ((string)(this["DefaultDomain"]));
|
||||
}
|
||||
set {
|
||||
this["DefaultDomain"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
@@ -2771,6 +2735,18 @@ namespace mRemoteNG.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool CloseCredentialUnlockerDialogAfterLastUnlock {
|
||||
get {
|
||||
return ((bool)(this["CloseCredentialUnlockerDialogAfterLastUnlock"]));
|
||||
}
|
||||
set {
|
||||
this["CloseCredentialUnlockerDialogAfterLastUnlock"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
@@ -2797,6 +2773,18 @@ namespace mRemoteNG.Properties {
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("00000000-0000-0000-0000-000000000000")]
|
||||
public global::System.Guid DefaultCredentialRecord {
|
||||
get {
|
||||
return ((global::System.Guid)(this["DefaultCredentialRecord"]));
|
||||
}
|
||||
set {
|
||||
this["DefaultCredentialRecord"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool AlwaysShowConnectionTabs {
|
||||
get {
|
||||
|
||||
@@ -59,15 +59,6 @@
|
||||
<Setting Name="EmptyCredentials" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)">noinfo</Value>
|
||||
</Setting>
|
||||
<Setting Name="DefaultUsername" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="DefaultPassword" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="DefaultDomain" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="UseCustomPuttyPath" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
@@ -689,11 +680,17 @@
|
||||
<Setting Name="StartUpPanelName" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)">General</Value>
|
||||
</Setting>
|
||||
<Setting Name="CloseCredentialUnlockerDialogAfterLastUnlock" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="TrackActiveConnectionInConnectionTree" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="PlaceSearchBarAboveConnectionTree" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="DefaultCredentialRecord" Type="System.Guid" Scope="User">
|
||||
<Value Profile="(Default)">00000000-0000-0000-0000-000000000000</Value>
|
||||
</Setting>
|
||||
<Setting Name="AlwaysShowConnectionTabs" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
|
||||
BIN
mRemoteNG/Resources/Edit_16x.png
Normal file
BIN
mRemoteNG/Resources/Edit_16x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 532 B |
BIN
mRemoteNG/Resources/Unlock_16x.png
Normal file
BIN
mRemoteNG/Resources/Unlock_16x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 307 B |
@@ -36,9 +36,9 @@
|
||||
<xs:attribute name="Descr" type="xs:string" use="required" />
|
||||
<xs:attribute name="Icon" type="xs:string" use="required" />
|
||||
<xs:attribute name="Panel" type="xs:string" use="required" />
|
||||
<xs:attribute name="Username" type="xs:string" use="required" />
|
||||
<xs:attribute name="Domain" type="xs:string" use="required" />
|
||||
<xs:attribute name="Password" type="xs:string" use="required" />
|
||||
|
||||
<xs:attribute name="CredentialId" type="xs:string" use="required" />
|
||||
|
||||
<xs:attribute name="Hostname" type="xs:string" use="required" />
|
||||
<xs:attribute name="Protocol" type="xs:string" use="required" />
|
||||
<xs:attribute name="RdpVersion" type="xs:string" use="required" />
|
||||
@@ -115,9 +115,9 @@
|
||||
<xs:attribute name="InheritDisableCursorShadow" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritDisableCursorBlinking" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritDomain" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritCredentialId" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritIcon" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritPanel" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritPassword" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritPort" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritProtocol" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritRdpVersion" type="xs:boolean" use="optional" />
|
||||
@@ -137,7 +137,6 @@
|
||||
<xs:attribute name="InheritAutomaticResize" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritUseConsoleSession" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritRenderingEngine" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritUsername" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritICAEncryptionStrength" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritRDPAuthenticationLevel" type="xs:boolean" use="optional" />
|
||||
<xs:attribute name="InheritRDPMinutesToIdleTimeout" type="xs:boolean" use="optional" />
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using mRemoteNG.Themes;
|
||||
|
||||
namespace mRemoteNG.Themes
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace mRemoteNG.Tools
|
||||
public MouseEventHandler MouseUpEventHandler { get; set; }
|
||||
|
||||
|
||||
public IEnumerable<ToolStripDropDownItem> CreateToolStripDropDownItems(ConnectionTreeModel connectionTreeModel)
|
||||
public IEnumerable<ToolStripDropDownItem> CreateToolStripDropDownItems(IConnectionTreeModel connectionTreeModel)
|
||||
{
|
||||
var rootNodes = connectionTreeModel.RootNodes;
|
||||
return CreateToolStripDropDownItems(rootNodes);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace mRemoteNG.Tools.CustomCollections
|
||||
where T : INotifyPropertyChanged
|
||||
{
|
||||
private readonly IList<T> _list = new List<T>();
|
||||
private bool _eventsAllowed;
|
||||
private bool _eventsAllowed = true;
|
||||
|
||||
public int Count => _list.Count;
|
||||
public bool IsReadOnly => _list.IsReadOnly;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Container;
|
||||
|
||||
namespace mRemoteNG.Tools
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static Optional<T> Maybe<T>(this T value)
|
||||
public static Optional<T> ToOptional<T>(this T value)
|
||||
{
|
||||
return new Optional<T>(value);
|
||||
}
|
||||
@@ -69,5 +71,19 @@ namespace mRemoteNG.Tools
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public static IEnumerable<ConnectionInfo> FlattenConnectionTree(this IEnumerable<ConnectionInfo> connections)
|
||||
{
|
||||
foreach (var item in connections)
|
||||
{
|
||||
yield return item;
|
||||
|
||||
if (!(item is ContainerInfo container))
|
||||
continue;
|
||||
|
||||
foreach (var child in FlattenConnectionTree(container.Children))
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace mRemoteNG.Tools
|
||||
{
|
||||
public class ExternalTool : INotifyPropertyChanged
|
||||
{
|
||||
private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator();
|
||||
private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
|
||||
private string _displayName;
|
||||
private string _fileName;
|
||||
private bool _waitForExit;
|
||||
@@ -152,7 +152,7 @@ namespace mRemoteNG.Tools
|
||||
|
||||
private void SetProcessProperties(Process process, ConnectionInfo startConnectionInfo)
|
||||
{
|
||||
var argParser = new ExternalToolArgumentParser(startConnectionInfo);
|
||||
var argParser = new ExternalToolArgumentParser(startConnectionInfo, Runtime.CredentialService);
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
process.StartInfo.FileName = argParser.ParseArguments(FileName);
|
||||
process.StartInfo.Arguments = argParser.ParseArguments(Arguments);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using mRemoteNG.App;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Properties;
|
||||
using mRemoteNG.Security.SymmetricEncryption;
|
||||
using mRemoteNG.Credential;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tools.Cmdline;
|
||||
|
||||
namespace mRemoteNG.Tools
|
||||
@@ -11,10 +12,12 @@ namespace mRemoteNG.Tools
|
||||
public class ExternalToolArgumentParser
|
||||
{
|
||||
private readonly ConnectionInfo _connectionInfo;
|
||||
private readonly ICredentialService _credentialService;
|
||||
|
||||
public ExternalToolArgumentParser(ConnectionInfo connectionInfo)
|
||||
public ExternalToolArgumentParser(ConnectionInfo connectionInfo, ICredentialService credentialService)
|
||||
{
|
||||
_connectionInfo = connectionInfo;
|
||||
_credentialService = credentialService.ThrowIfNull(nameof(credentialService));
|
||||
}
|
||||
|
||||
public string ParseArguments(string input)
|
||||
@@ -162,56 +165,55 @@ namespace mRemoteNG.Tools
|
||||
|
||||
private string GetVariableReplacement(string variable, string original)
|
||||
{
|
||||
var replacement = "";
|
||||
if (_connectionInfo == null) return replacement;
|
||||
switch (variable.ToLowerInvariant())
|
||||
{
|
||||
case "name":
|
||||
replacement = _connectionInfo.Name;
|
||||
break;
|
||||
case "hostname":
|
||||
replacement = _connectionInfo.Hostname;
|
||||
break;
|
||||
case "port":
|
||||
replacement = Convert.ToString(_connectionInfo.Port);
|
||||
break;
|
||||
case "username":
|
||||
replacement = _connectionInfo.Username;
|
||||
if (string.IsNullOrEmpty(replacement))
|
||||
if (Settings.Default.EmptyCredentials == "windows")
|
||||
replacement = Environment.UserName;
|
||||
else if (Settings.Default.EmptyCredentials == "custom")
|
||||
replacement = Settings.Default.DefaultUsername;
|
||||
break;
|
||||
case "password":
|
||||
replacement = _connectionInfo.Password;
|
||||
if (string.IsNullOrEmpty(replacement) && Settings.Default.EmptyCredentials == "custom")
|
||||
replacement = new LegacyRijndaelCryptographyProvider()
|
||||
.Decrypt(Convert.ToString(Settings.Default.DefaultPassword),
|
||||
Runtime.EncryptionKey);
|
||||
break;
|
||||
case "domain":
|
||||
replacement = _connectionInfo.Domain;
|
||||
if (string.IsNullOrEmpty(replacement))
|
||||
if (Settings.Default.EmptyCredentials == "windows")
|
||||
replacement = Environment.UserDomainName;
|
||||
else if (Settings.Default.EmptyCredentials == "custom")
|
||||
replacement = Settings.Default.DefaultDomain;
|
||||
break;
|
||||
case "description":
|
||||
replacement = _connectionInfo.Description;
|
||||
break;
|
||||
case "macaddress":
|
||||
replacement = _connectionInfo.MacAddress;
|
||||
break;
|
||||
case "userfield":
|
||||
replacement = _connectionInfo.UserField;
|
||||
break;
|
||||
default:
|
||||
return original;
|
||||
}
|
||||
var normalizedVariable = variable.ToLowerInvariant();
|
||||
|
||||
return replacement;
|
||||
if (normalizedVariable == "name")
|
||||
return _connectionInfo?.Name ?? "";
|
||||
if (normalizedVariable == "hostname")
|
||||
return _connectionInfo?.Hostname ?? "";
|
||||
if (normalizedVariable == "port")
|
||||
return _connectionInfo?.Port.ToString() ?? "";
|
||||
if (normalizedVariable == "description")
|
||||
return _connectionInfo?.Description ?? "";
|
||||
if (normalizedVariable == "macaddress")
|
||||
return _connectionInfo?.MacAddress ?? "";
|
||||
if (normalizedVariable == "userfield")
|
||||
return _connectionInfo?.UserField ?? "";
|
||||
if (normalizedVariable.StartsWith("username"))
|
||||
return GetCredentialToUse(normalizedVariable)
|
||||
.FirstOrDefault()?
|
||||
.Username;
|
||||
if (normalizedVariable.StartsWith("password"))
|
||||
return GetCredentialToUse(normalizedVariable)
|
||||
.FirstOrDefault()?
|
||||
.Password.ConvertToUnsecureString();
|
||||
if (normalizedVariable.StartsWith("domain"))
|
||||
return GetCredentialToUse(normalizedVariable)
|
||||
.FirstOrDefault()?
|
||||
.Domain;
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
private Optional<ICredentialRecord> GetCredentialToUse(string variable)
|
||||
{
|
||||
var specifiedCred = GetSpecifiedCredential(variable);
|
||||
return specifiedCred.Any()
|
||||
? specifiedCred
|
||||
: _credentialService.GetEffectiveCredentialRecord(_connectionInfo?.CredentialRecordId ?? Optional<Guid>.Empty);
|
||||
}
|
||||
|
||||
private Optional<ICredentialRecord> GetSpecifiedCredential(string variable)
|
||||
{
|
||||
var match = Regex.Match(variable.Replace("-", string.Empty), @":(?<id>[0-9a-fA-F]{7,32})$");
|
||||
if (!match.Success)
|
||||
return Optional<ICredentialRecord>.Empty;
|
||||
|
||||
var id = match.Groups["id"].Value;
|
||||
return _credentialService
|
||||
.GetCredentialRecords()
|
||||
.FirstOrDefault(r => r.Id.ToString("N").StartsWith(id))
|
||||
.ToOptional();
|
||||
}
|
||||
|
||||
private string PerformReplacements(string input, List<Replacement> replacements)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using Microsoft.Win32;
|
||||
using mRemoteNG.App;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace mRemoteNG.Tools
|
||||
private readonly NotifyIcon _nI;
|
||||
private readonly ContextMenuStrip _cMen;
|
||||
private readonly ToolStripMenuItem _cMenCons;
|
||||
private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator();
|
||||
private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
|
||||
private static readonly FrmMain FrmMain = FrmMain.Default;
|
||||
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
@@ -43,7 +43,8 @@ namespace mRemoteNG.Tools
|
||||
return new Optional<T>(value);
|
||||
}
|
||||
|
||||
public static Optional<TOut> FromNullable<TOut>(TOut? value) where TOut : struct
|
||||
public static Optional<TOut> FromNullable<TOut>(TOut? value)
|
||||
where TOut : struct
|
||||
{
|
||||
return value.HasValue
|
||||
? new Optional<TOut>(value.Value)
|
||||
|
||||
@@ -9,7 +9,7 @@ using mRemoteNG.Tree.Root;
|
||||
|
||||
namespace mRemoteNG.Tree
|
||||
{
|
||||
public sealed class ConnectionTreeModel : INotifyCollectionChanged, INotifyPropertyChanged
|
||||
public sealed class ConnectionTreeModel : IConnectionTreeModel
|
||||
{
|
||||
public List<ContainerInfo> RootNodes { get; } = new List<ContainerInfo>();
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace mRemoteNG.Tree
|
||||
|
||||
public IEnumerable<ConnectionInfo> GetRecursiveChildList(ContainerInfo container)
|
||||
{
|
||||
return container.GetRecursiveChildList();
|
||||
return container?.GetRecursiveChildList() ?? new ConnectionInfo[0];
|
||||
}
|
||||
|
||||
public IEnumerable<ConnectionInfo> GetRecursiveFavoriteChildList(ContainerInfo container)
|
||||
|
||||
20
mRemoteNG/Tree/IConnectionTreeModel.cs
Normal file
20
mRemoteNG/Tree/IConnectionTreeModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Container;
|
||||
|
||||
namespace mRemoteNG.Tree
|
||||
{
|
||||
public interface IConnectionTreeModel : INotifyCollectionChanged, INotifyPropertyChanged
|
||||
{
|
||||
List<ContainerInfo> RootNodes { get; }
|
||||
void AddRootNode(ContainerInfo rootNode);
|
||||
void RemoveRootNode(ContainerInfo rootNode);
|
||||
IReadOnlyList<ConnectionInfo> GetRecursiveChildList();
|
||||
IEnumerable<ConnectionInfo> GetRecursiveChildList(ContainerInfo container);
|
||||
void RenameNode(ConnectionInfo connectionInfo, string newName);
|
||||
void DeleteNode(ConnectionInfo connectionInfo);
|
||||
IEnumerable<ConnectionInfo> GetRecursiveFavoriteChildList(ContainerInfo node);
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ namespace mRemoteNG.Tree
|
||||
{
|
||||
public class NodeSearcher
|
||||
{
|
||||
private readonly ConnectionTreeModel _connectionTreeModel;
|
||||
private readonly IConnectionTreeModel _connectionTreeModel;
|
||||
|
||||
private List<ConnectionInfo> Matches { get; set; }
|
||||
public ConnectionInfo CurrentMatch { get; private set; }
|
||||
|
||||
|
||||
public NodeSearcher(ConnectionTreeModel connectionTreeModel)
|
||||
public NodeSearcher(IConnectionTreeModel connectionTreeModel)
|
||||
{
|
||||
_connectionTreeModel = connectionTreeModel;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,7 @@ namespace mRemoteNG.UI.Controls.Adapters
|
||||
_editorService = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
|
||||
if (_editorService == null) return value;
|
||||
|
||||
var credentialManager = Runtime.CredentialProviderCatalog;
|
||||
|
||||
var listBox = new CredentialRecordListBox(credentialManager.GetCredentialRecords());
|
||||
var listBox = new CredentialRecordListBox(Runtime.CredentialService.RepositoryList.GetCredentialRecords());
|
||||
listBox.SelectedValueChanged += ListBoxOnSelectedValueChanged;
|
||||
|
||||
_editorService.DropDownControl(listBox);
|
||||
|
||||
@@ -6,6 +6,7 @@ using mRemoteNG.App;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Container;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tools.Clipboard;
|
||||
using mRemoteNG.Tree;
|
||||
@@ -57,7 +58,7 @@ namespace mRemoteNG.UI.Controls
|
||||
public ConnectionContextMenu(ConnectionTree.ConnectionTree connectionTree)
|
||||
{
|
||||
_connectionTree = connectionTree;
|
||||
_connectionInitiator = new ConnectionInitiator();
|
||||
_connectionInitiator = new ConnectionInitiator(Runtime.CredentialService);
|
||||
InitializeComponent();
|
||||
ApplyLanguage();
|
||||
EnableShortcutKeys();
|
||||
@@ -761,8 +762,8 @@ namespace mRemoteNG.UI.Controls
|
||||
{
|
||||
Windows.Show(WindowType.SSHTransfer);
|
||||
Windows.SshtransferForm.Hostname = _connectionTree.SelectedNode.Hostname;
|
||||
Windows.SshtransferForm.Username = _connectionTree.SelectedNode.Username;
|
||||
Windows.SshtransferForm.Password = _connectionTree.SelectedNode.Password;
|
||||
Windows.SshtransferForm.Username = _connectionTree.SelectedNode.CredentialRecord.Username;
|
||||
Windows.SshtransferForm.Password = _connectionTree.SelectedNode.CredentialRecord.Password.ConvertToUnsecureString();
|
||||
Windows.SshtransferForm.Port = Convert.ToString(_connectionTree.SelectedNode.Port);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -15,7 +15,6 @@ using mRemoteNG.Tools.Clipboard;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.ClickHandlers;
|
||||
using mRemoteNG.Tree.Root;
|
||||
|
||||
// ReSharper disable ArrangeAccessorOwnerBody
|
||||
|
||||
namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
@@ -33,7 +32,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
private bool _nodeInEditMode;
|
||||
private bool _allowEdit;
|
||||
private ConnectionContextMenu _contextMenu;
|
||||
private ConnectionTreeModel _connectionTreeModel;
|
||||
private IConnectionTreeModel _connectionTreeModel;
|
||||
|
||||
public ConnectionInfo SelectedNode => (ConnectionInfo)SelectedObject;
|
||||
|
||||
@@ -49,7 +48,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
public ITreeNodeClickHandler<ConnectionInfo> SingleClickHandler { get; set; } =
|
||||
new TreeNodeCompositeClickHandler();
|
||||
|
||||
public ConnectionTreeModel ConnectionTreeModel
|
||||
public IConnectionTreeModel ConnectionTreeModel
|
||||
{
|
||||
get { return _connectionTreeModel; }
|
||||
set
|
||||
@@ -196,7 +195,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
padding;
|
||||
}
|
||||
|
||||
private void PopulateTreeView(ConnectionTreeModel newModel)
|
||||
private void PopulateTreeView(IConnectionTreeModel newModel)
|
||||
{
|
||||
SetObjects(newModel.RootNodes);
|
||||
RegisterModelUpdateHandlers(newModel);
|
||||
@@ -205,14 +204,14 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
AutoResizeColumn(Columns[0]);
|
||||
}
|
||||
|
||||
private void RegisterModelUpdateHandlers(ConnectionTreeModel newModel)
|
||||
private void RegisterModelUpdateHandlers(IConnectionTreeModel newModel)
|
||||
{
|
||||
_puttySessionsManager.PuttySessionsCollectionChanged += OnPuttySessionsCollectionChanged;
|
||||
newModel.CollectionChanged += HandleCollectionChanged;
|
||||
newModel.PropertyChanged += HandleCollectionPropertyChanged;
|
||||
}
|
||||
|
||||
private void UnregisterModelUpdateHandlers(ConnectionTreeModel oldConnectionTreeModel)
|
||||
private void UnregisterModelUpdateHandlers(IConnectionTreeModel oldConnectionTreeModel)
|
||||
{
|
||||
_puttySessionsManager.PuttySessionsCollectionChanged -= OnPuttySessionsCollectionChanged;
|
||||
|
||||
@@ -261,12 +260,13 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
|
||||
public RootNodeInfo GetRootConnectionNode()
|
||||
{
|
||||
return (RootNodeInfo)ConnectionTreeModel.RootNodes.First(item => item is RootNodeInfo);
|
||||
return (RootNodeInfo)ConnectionTreeModel.RootNodes.FirstOrDefault(item => item is RootNodeInfo);
|
||||
}
|
||||
|
||||
public void Invoke(Action action)
|
||||
{
|
||||
Invoke((Delegate)action);
|
||||
if (Created)
|
||||
Invoke((Delegate)action);
|
||||
}
|
||||
|
||||
public void InvokeExpand(object model)
|
||||
@@ -433,7 +433,11 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
ResetColumnFiltering();
|
||||
}
|
||||
|
||||
RefreshObject(sender);
|
||||
if (sender is IConnectionTree)
|
||||
RebuildAll(true);
|
||||
else
|
||||
RefreshObject(sender);
|
||||
|
||||
AutoResizeColumn(Columns[0]);
|
||||
|
||||
// turn filtering back on
|
||||
@@ -545,4 +549,4 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
{
|
||||
public interface IConnectionTree
|
||||
{
|
||||
ConnectionTreeModel ConnectionTreeModel { get; set; }
|
||||
IConnectionTreeModel ConnectionTreeModel { get; set; }
|
||||
|
||||
ConnectionInfo SelectedNode { get; }
|
||||
|
||||
|
||||
@@ -6,7 +6,17 @@ namespace mRemoteNG.UI.Controls
|
||||
{
|
||||
public partial class CredentialRecordComboBox : ComboBox
|
||||
{
|
||||
public IEnumerable<ICredentialRecord> CredentialRecords { get; set; }
|
||||
private IEnumerable<ICredentialRecord> _credentialRecords;
|
||||
|
||||
public IEnumerable<ICredentialRecord> CredentialRecords
|
||||
{
|
||||
get => _credentialRecords;
|
||||
set
|
||||
{
|
||||
_credentialRecords = value;
|
||||
PopulateItems(_credentialRecords);
|
||||
}
|
||||
}
|
||||
|
||||
public CredentialRecordComboBox()
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace mRemoteNG.UI.Controls
|
||||
public partial class CredentialRecordListBox : ListBox
|
||||
{
|
||||
public new ICredentialRecord SelectedItem => (ICredentialRecord)base.SelectedItem;
|
||||
public ICredentialRecord NoneSelection { get; } = new CredentialRecord {Title = $"--{Language.None}--"};
|
||||
public ICredentialRecord NoneSelection { get; } = new NullCredentialRecord();
|
||||
public ICredentialRecord AddNewSelection { get; } = new CredentialRecord {Title = $"--{Language.Add}--"};
|
||||
|
||||
public CredentialRecordListBox(IEnumerable<ICredentialRecord> listOfCredentialRecords)
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
this.objectListView1.AllColumns.Add(this.olvColumnCredentialId);
|
||||
this.objectListView1.AllColumns.Add(this.olvColumnRepositoryTitle);
|
||||
this.objectListView1.AllColumns.Add(this.olvColumnRepositorySource);
|
||||
this.objectListView1.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||
this.objectListView1.CellEditUseWholeCell = false;
|
||||
this.objectListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.olvColumnTitle,
|
||||
@@ -108,7 +109,6 @@
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.Controls.Add(this.objectListView1);
|
||||
this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.Name = "CredentialRecordListView";
|
||||
this.Size = new System.Drawing.Size(367, 204);
|
||||
((System.ComponentModel.ISupportInitialize)(this.objectListView1)).EndInit();
|
||||
|
||||
59
mRemoteNG/UI/Controls/MrngToolStripSplitButton.cs
Normal file
59
mRemoteNG/UI/Controls/MrngToolStripSplitButton.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace mRemoteNG.UI.Controls
|
||||
{
|
||||
public class MrngToolStripSplitButton : System.Windows.Forms.ToolStripSplitButton
|
||||
{
|
||||
public new ToolStripDropDown DropDown
|
||||
{
|
||||
get { return base.DropDown; }
|
||||
set
|
||||
{
|
||||
if (base.DropDown != value)
|
||||
{
|
||||
base.DropDown = value;
|
||||
base.DropDown.Closing += DropDown_Closing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e)
|
||||
{
|
||||
if (e.CloseReason != ToolStripDropDownCloseReason.AppClicked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Rectangle dropDownButtonBoundsClient = DropDownButtonBounds; // Relative to the ToolStripSplitButton
|
||||
dropDownButtonBoundsClient.Offset(Bounds.Location); // Relative to the parent of the ToolStripSplitButton
|
||||
Rectangle dropDownButtonBoundsScreen =
|
||||
GetCurrentParent().RectangleToScreen(dropDownButtonBoundsClient); // Relative to the screen
|
||||
|
||||
if (dropDownButtonBoundsScreen.Contains(Control.MousePosition))
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseEventArgs e)
|
||||
{
|
||||
_dropDownVisibleOnMouseDown = DropDown.Visible;
|
||||
base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseEventArgs e)
|
||||
{
|
||||
if (_dropDownVisibleOnMouseDown)
|
||||
{
|
||||
DropDown.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _dropDownVisibleOnMouseDown;
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,10 @@ namespace mRemoteNG.UI.Controls
|
||||
public NewPasswordWithVerification()
|
||||
{
|
||||
InitializeComponent();
|
||||
ApplyLanguage();
|
||||
var display = new DisplayProperties();
|
||||
secureTextBox1.Width = display.ScaleWidth(Width);
|
||||
secureTextBox2.Width = display.ScaleWidth(Width);
|
||||
secureTextBox1.TextChanged += OnSecureTextBoxTextChanged;
|
||||
secureTextBox2.TextChanged += OnSecureTextBoxTextChanged;
|
||||
}
|
||||
@@ -55,6 +59,14 @@ namespace mRemoteNG.UI.Controls
|
||||
secureTextBox2.Text = text;
|
||||
}
|
||||
|
||||
private void ApplyLanguage()
|
||||
{
|
||||
//TODO: RESX
|
||||
//labelFirstPasswordBox.Text = Language.strNewPassword;
|
||||
//labelSecondPasswordBox.Text = Language.strVerifyPassword;
|
||||
//labelPasswordsDontMatch.Text = Language.strPasswordsDontMatch;
|
||||
}
|
||||
|
||||
private bool Verify()
|
||||
{
|
||||
return secureTextBox1.SecString.Length == secureTextBox2.SecString.Length &&
|
||||
@@ -90,16 +102,28 @@ namespace mRemoteNG.UI.Controls
|
||||
{
|
||||
PasswordsMatch = false;
|
||||
SecureString = null;
|
||||
RaiseNotVerifiedEvent();
|
||||
}
|
||||
|
||||
TogglePasswordMatchIndicator(PasswordsMatch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the two given passwords go from not matching to matching (verified).
|
||||
/// </summary>
|
||||
public event EventHandler Verified;
|
||||
|
||||
private void RaiseVerifiedEvent()
|
||||
{
|
||||
Verified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the two given passwords go from matching to not matching (not verified).
|
||||
/// </summary>
|
||||
public event EventHandler NotVerified;
|
||||
private void RaiseNotVerifiedEvent()
|
||||
{
|
||||
NotVerified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user