mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-17 14:07:46 +08:00
Finalize Credentials Options Page
This commit finalizes the Credentials Options Page with the following changes: - Updated and added comments to improve code readability and understanding. - Enabled the password storing ability, making it functional but not yet usable. - Applied some code optimizations for improved efficiency.
This commit is contained in:
4
mRemoteNG/Language/Language.Designer.cs
generated
4
mRemoteNG/Language/Language.Designer.cs
generated
@@ -3399,9 +3399,9 @@ namespace mRemoteNG.Resources.Language {
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to *Some settings are determined by your company. For further information, please contact your administrator.
|
||||
/// </summary>
|
||||
internal static string OptionsAdminInfo {
|
||||
internal static string OptionsCompanyPolicyMessage {
|
||||
get {
|
||||
return ResourceManager.GetString("OptionsAdminInfo", resourceCulture);
|
||||
return ResourceManager.GetString("OptionsCompanyPolicyMessage", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2047,7 +2047,7 @@ Nightly umfasst Alphas, Betas und Release Candidates.</value>
|
||||
<value>Maximale Anmeldeversuche erreicht. Verbindung erneut initiieren.</value>
|
||||
<comment>C# to Powershell transfer issue with encoding possible</comment>
|
||||
</data>
|
||||
<data name="OptionsAdminInfo" xml:space="preserve">
|
||||
<data name="OptionsCompanyPolicyMessage" xml:space="preserve">
|
||||
<value>*Einige Einstellungen werden von Ihrem Unternehmen festgelegt. Für weitere Informationen wenden Sie sich an Ihren Systemadministrator.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2365,7 +2365,7 @@ Nightly Channel includes Alphas, Betas & Release Candidates.</value>
|
||||
<data name="FilterSecureCRT" xml:space="preserve">
|
||||
<value>SecureCRT (*.xml)</value>
|
||||
</data>
|
||||
<data name="OptionsAdminInfo" xml:space="preserve">
|
||||
<data name="OptionsCompanyPolicyMessage" xml:space="preserve">
|
||||
<value>*Some settings are determined by your company. For further information, please contact your administrator</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -43,7 +43,7 @@
|
||||
lblCredentialsDomain = new Controls.MrngLabel();
|
||||
radCredentialsNoInfo = new Controls.MrngRadioButton();
|
||||
radCredentialsCustom = new Controls.MrngRadioButton();
|
||||
lblCredentialsAdminInfo = new System.Windows.Forms.Label();
|
||||
lblRegistrySettingsUsedInfo = new System.Windows.Forms.Label();
|
||||
lblCredentialsGeneratorHelp = new Controls.MrngLabel();
|
||||
btnCredentialsGenerator = new System.Windows.Forms.Button();
|
||||
lblCredentialsGenerator = new Controls.MrngLabel();
|
||||
@@ -56,7 +56,7 @@
|
||||
// pnlDefaultCredentials
|
||||
//
|
||||
pnlDefaultCredentials.Controls.Add(pnlCredentialsSettingsPanel);
|
||||
pnlDefaultCredentials.Controls.Add(lblCredentialsAdminInfo);
|
||||
pnlDefaultCredentials.Controls.Add(lblRegistrySettingsUsedInfo);
|
||||
pnlDefaultCredentials.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
pnlDefaultCredentials.Location = new System.Drawing.Point(0, 0);
|
||||
pnlDefaultCredentials.Name = "pnlDefaultCredentials";
|
||||
@@ -149,6 +149,7 @@
|
||||
txtCredentialsPassword.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
txtCredentialsPassword.Location = new System.Drawing.Point(163, 55);
|
||||
txtCredentialsPassword.Name = "txtCredentialsPassword";
|
||||
txtCredentialsPassword.PasswordChar = '*';
|
||||
txtCredentialsPassword.Size = new System.Drawing.Size(166, 22);
|
||||
txtCredentialsPassword.TabIndex = 7;
|
||||
//
|
||||
@@ -232,18 +233,18 @@
|
||||
radCredentialsCustom.UseVisualStyleBackColor = false;
|
||||
radCredentialsCustom.CheckedChanged += radCredentialsCustom_CheckedChanged;
|
||||
//
|
||||
// lblCredentialsAdminInfo
|
||||
// lblRegistrySettingsUsedInfo
|
||||
//
|
||||
lblCredentialsAdminInfo.BackColor = System.Drawing.SystemColors.ControlLight;
|
||||
lblCredentialsAdminInfo.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
lblCredentialsAdminInfo.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
lblCredentialsAdminInfo.Location = new System.Drawing.Point(0, 0);
|
||||
lblCredentialsAdminInfo.Name = "lblCredentialsAdminInfo";
|
||||
lblCredentialsAdminInfo.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0);
|
||||
lblCredentialsAdminInfo.Size = new System.Drawing.Size(610, 30);
|
||||
lblCredentialsAdminInfo.TabIndex = 0;
|
||||
lblCredentialsAdminInfo.Text = "Some settings are configured by your Administrator. Please contact your administrator for more information.";
|
||||
lblCredentialsAdminInfo.Visible = false;
|
||||
lblRegistrySettingsUsedInfo.BackColor = System.Drawing.SystemColors.ControlLight;
|
||||
lblRegistrySettingsUsedInfo.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
lblRegistrySettingsUsedInfo.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
lblRegistrySettingsUsedInfo.Location = new System.Drawing.Point(0, 0);
|
||||
lblRegistrySettingsUsedInfo.Name = "lblRegistrySettingsUsedInfo";
|
||||
lblRegistrySettingsUsedInfo.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0);
|
||||
lblRegistrySettingsUsedInfo.Size = new System.Drawing.Size(610, 30);
|
||||
lblRegistrySettingsUsedInfo.TabIndex = 0;
|
||||
lblRegistrySettingsUsedInfo.Text = "Some settings are configured by your Administrator. Please contact your administrator for more information.";
|
||||
lblRegistrySettingsUsedInfo.Visible = false;
|
||||
//
|
||||
// lblCredentialsGeneratorHelp
|
||||
//
|
||||
@@ -304,7 +305,7 @@
|
||||
internal Controls.MrngTextBox txtCredentialsUsername;
|
||||
internal Controls.MrngLabel lblCredentialsDomain;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
internal System.Windows.Forms.Label lblCredentialsAdminInfo;
|
||||
internal System.Windows.Forms.Label lblRegistrySettingsUsedInfo;
|
||||
internal System.Windows.Forms.Panel pnlCredentialsSettingsPanel;
|
||||
internal Controls.MrngLabel lblCredentialsGenerator;
|
||||
internal System.Windows.Forms.TextBox txtCredentialsGeneratorPsswd;
|
||||
|
||||
@@ -5,6 +5,7 @@ using mRemoteNG.Security.SymmetricEncryption;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using System.Runtime.Versioning;
|
||||
using mRemoteNG.Config.Settings.Registry;
|
||||
using System.DirectoryServices;
|
||||
|
||||
namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
{
|
||||
@@ -13,8 +14,6 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
{
|
||||
#region Private Fields
|
||||
private readonly OptRegistryCredentialsPage _RegistrySettings = new();
|
||||
private bool _pageEnabled = true;
|
||||
|
||||
#endregion
|
||||
|
||||
public CredentialsPage()
|
||||
@@ -40,12 +39,13 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
lblCredentialsUsername.Text = Language.Username;
|
||||
lblCredentialsPassword.Text = Language.Password;
|
||||
lblCredentialsDomain.Text = Language.Domain;
|
||||
lblCredentialsAdminInfo.Text = Language.OptionsAdminInfo;
|
||||
lblRegistrySettingsUsedInfo.Text = Language.OptionsCompanyPolicyMessage;
|
||||
}
|
||||
|
||||
public override void LoadSettings()
|
||||
{
|
||||
if (!_pageEnabled) { return; }
|
||||
if (!_RegistrySettings.CredentialPageEnabled)
|
||||
return;
|
||||
|
||||
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||
switch (Properties.OptionsCredentialsPage.Default.EmptyCredentials)
|
||||
@@ -92,13 +92,19 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
|
||||
public override void LoadRegistrySettings()
|
||||
{
|
||||
if (!CommonRegistrySettings.AllowModifyCredentialSettings)
|
||||
// CredentialPageEnabled reg setting: enabled/default: true; Disabled: false.
|
||||
if (!_RegistrySettings.CredentialPageEnabled)
|
||||
{
|
||||
DisablePage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_RegistrySettings.UseCredentials.IsKeyPresent)
|
||||
// UseCredentials reg setting with validation:
|
||||
// 1. Is not set or valid, stop processing.
|
||||
// 2. Set the 'EmptyCredentials' option based on value
|
||||
// 3. Only proceed when "custom"
|
||||
if (!_RegistrySettings.UseCredentials.IsValid) { return; }
|
||||
else if (_RegistrySettings.UseCredentials.IsValid)
|
||||
{
|
||||
Properties.OptionsCredentialsPage.Default.EmptyCredentials = _RegistrySettings.UseCredentials.Value;
|
||||
|
||||
@@ -107,11 +113,13 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
case "noinfo":
|
||||
DisableControl(radCredentialsWindows);
|
||||
DisableControl(radCredentialsCustom);
|
||||
break;
|
||||
SetVisibilitySettingsUsedInfo();
|
||||
return;
|
||||
case "windows":
|
||||
DisableControl(radCredentialsNoInfo);
|
||||
DisableControl(radCredentialsCustom);
|
||||
break;
|
||||
SetVisibilitySettingsUsedInfo();
|
||||
return;
|
||||
case "custom":
|
||||
DisableControl(radCredentialsNoInfo);
|
||||
DisableControl(radCredentialsWindows);
|
||||
@@ -119,95 +127,87 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
}
|
||||
}
|
||||
|
||||
if (_RegistrySettings.UseCredentials.Value != "custom") { return; }
|
||||
// ***
|
||||
// The following is only used when set to custom!
|
||||
|
||||
|
||||
if (!CommonRegistrySettings.AllowSaveUsernames)
|
||||
{
|
||||
Properties.OptionsCredentialsPage.Default.DefaultUsername = "";
|
||||
DisableControl(txtCredentialsUsername);
|
||||
}
|
||||
else if (_RegistrySettings.DefaultUsername.IsKeyPresent)
|
||||
// DefaultUsername reg setting: set DefaultUsername option based on value
|
||||
if (_RegistrySettings.DefaultUsername.IsSet)
|
||||
{
|
||||
Properties.OptionsCredentialsPage.Default.DefaultUsername = _RegistrySettings.DefaultUsername.Value;
|
||||
DisableControl(txtCredentialsUsername);
|
||||
}
|
||||
|
||||
if (!CommonRegistrySettings.AllowSavePasswords)
|
||||
// DefaultPassword reg setting:
|
||||
// 1. Test decription works to prevents potential issues
|
||||
// 2. Set DefaultPassword option based on value
|
||||
// 3. Clears reg setting if fails
|
||||
if (_RegistrySettings.DefaultPassword.IsSet)
|
||||
{
|
||||
Properties.OptionsCredentialsPage.Default.DefaultPassword = "";
|
||||
try
|
||||
{
|
||||
LegacyRijndaelCryptographyProvider cryptographyProvider = new();
|
||||
string decryptedPassword;
|
||||
string defaultPassword = _RegistrySettings.DefaultPassword.Value;
|
||||
|
||||
decryptedPassword = cryptographyProvider.Decrypt(defaultPassword, Runtime.EncryptionKey);
|
||||
Properties.OptionsCredentialsPage.Default.DefaultPassword = defaultPassword;
|
||||
DisableControl(txtCredentialsPassword);
|
||||
}
|
||||
//else if (_RegistrySettings.DefaultPassword.IsKeyPresent)
|
||||
//{
|
||||
// Properties.OptionsCredentialsPage.Default.DefaultPassword = _RegistrySettings.DefaultPassword.Value;
|
||||
// DisableControl(txtCredentialsPassword);
|
||||
//}
|
||||
catch
|
||||
{
|
||||
// Fire-and-forget: The DefaultPassword in the registry is not encrypted.
|
||||
_RegistrySettings.DefaultPassword.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (_RegistrySettings.DefaultDomain.IsKeyPresent)
|
||||
// DefaultDomain reg setting: set DefaultDomain option based on value
|
||||
if (_RegistrySettings.DefaultDomain.IsSet)
|
||||
{
|
||||
Properties.OptionsCredentialsPage.Default.DefaultDomain = _RegistrySettings.DefaultDomain.Value;
|
||||
DisableControl(txtCredentialsDomain);
|
||||
}
|
||||
|
||||
if (!CommonRegistrySettings.AllowSaveUsernames)
|
||||
{
|
||||
Properties.OptionsCredentialsPage.Default.UserViaAPIDefault = "";
|
||||
DisableControl(txtCredentialsUserViaAPI);
|
||||
}
|
||||
else if (_RegistrySettings.UserViaAPIDefault.IsKeyPresent)
|
||||
// UserViaAPIDefault reg setting: set UserViaAPIDefault option based on value
|
||||
if (_RegistrySettings.UserViaAPIDefault.IsSet)
|
||||
{
|
||||
Properties.OptionsCredentialsPage.Default.UserViaAPIDefault = _RegistrySettings.UserViaAPIDefault.Value;
|
||||
DisableControl(txtCredentialsUserViaAPI);
|
||||
}
|
||||
|
||||
lblCredentialsAdminInfo.Visible = ShowAdministratorInfo();
|
||||
SetVisibilitySettingsUsedInfo();
|
||||
}
|
||||
|
||||
public override bool ShowAdministratorInfo()
|
||||
/// <summary>
|
||||
/// Checks if any credantil registry settings are being used.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if any relevant registry settings are used; otherwise, false.
|
||||
/// </returns>
|
||||
public override bool ShowRegistrySettingsUsedInfo()
|
||||
{
|
||||
return !CommonRegistrySettings.AllowModifyCredentialSettings
|
||||
|| !CommonRegistrySettings.AllowExportPasswords
|
||||
return !CommonRegistrySettings.AllowExportPasswords
|
||||
|| !CommonRegistrySettings.AllowExportUsernames
|
||||
|| !CommonRegistrySettings.AllowSavePasswords
|
||||
|| !CommonRegistrySettings.AllowSaveUsernames
|
||||
|| _RegistrySettings.DefaultUsername.IsKeyPresent
|
||||
//|| _RegistrySettings.DefaultPassword.IsKeyPresent
|
||||
|| _RegistrySettings.DefaultDomain.IsKeyPresent
|
||||
|| _RegistrySettings.UserViaAPIDefault.IsKeyPresent;
|
||||
}
|
||||
|
||||
private void radCredentialsCustom_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!_RegistrySettings.DefaultUsername.IsKeyPresent && CommonRegistrySettings.AllowSaveUsernames)
|
||||
{
|
||||
lblCredentialsUsername.Enabled = radCredentialsCustom.Checked;
|
||||
txtCredentialsUsername.Enabled = radCredentialsCustom.Checked;
|
||||
}
|
||||
|
||||
if (/*!_RegistrySettings.DefaultPassword.IsKeyPresent &&*/ CommonRegistrySettings.AllowSavePasswords)
|
||||
{
|
||||
lblCredentialsPassword.Enabled = radCredentialsCustom.Checked;
|
||||
txtCredentialsPassword.Enabled = radCredentialsCustom.Checked;
|
||||
}
|
||||
|
||||
if (!_RegistrySettings.DefaultDomain.IsKeyPresent)
|
||||
{
|
||||
lblCredentialsDomain.Enabled = radCredentialsCustom.Checked;
|
||||
txtCredentialsDomain.Enabled = radCredentialsCustom.Checked;
|
||||
}
|
||||
|
||||
if (!_RegistrySettings.UserViaAPIDefault.IsKeyPresent)
|
||||
{
|
||||
lblCredentialsUserViaAPI.Enabled = radCredentialsCustom.Checked;
|
||||
txtCredentialsUserViaAPI.Enabled = radCredentialsCustom.Checked;
|
||||
}
|
||||
|| !_RegistrySettings.CredentialPageEnabled
|
||||
|| _RegistrySettings.UseCredentials.IsValid;
|
||||
|
||||
/*
|
||||
* Checking these values is unnecessary because UseCredentials must be valid and set to Custom.
|
||||
*
|
||||
||_RegistrySettings.DefaultUsername.IsSet
|
||||
|| _RegistrySettings.DefaultPassword.IsSet
|
||||
|| _RegistrySettings.DefaultDomain.IsSet
|
||||
|| _RegistrySettings.UserViaAPIDefault.IsSet;
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the page by setting default values and disabling controls.
|
||||
/// </summary>
|
||||
public override void DisablePage()
|
||||
{
|
||||
Properties.OptionsCredentialsPage.Default.EmptyCredentials = "noinfo";
|
||||
//radCredentialsNoInfo.Enabled = false;
|
||||
radCredentialsWindows.Enabled = false;
|
||||
radCredentialsCustom.Enabled = false;
|
||||
|
||||
@@ -216,8 +216,50 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
txtCredentialsDomain.Enabled = false;
|
||||
txtCredentialsUserViaAPI.Enabled = false;
|
||||
|
||||
lblCredentialsAdminInfo.Visible = true;
|
||||
_pageEnabled = false;
|
||||
SetVisibilitySettingsUsedInfo();
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void radCredentialsCustom_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!_RegistrySettings.DefaultUsername.IsSet && CommonRegistrySettings.AllowSaveUsernames)
|
||||
{
|
||||
lblCredentialsUsername.Enabled = radCredentialsCustom.Checked;
|
||||
txtCredentialsUsername.Enabled = radCredentialsCustom.Checked;
|
||||
}
|
||||
|
||||
if (!_RegistrySettings.DefaultPassword.IsSet && CommonRegistrySettings.AllowSavePasswords)
|
||||
{
|
||||
lblCredentialsPassword.Enabled = radCredentialsCustom.Checked;
|
||||
txtCredentialsPassword.Enabled = radCredentialsCustom.Checked;
|
||||
}
|
||||
|
||||
if (!_RegistrySettings.DefaultDomain.IsSet)
|
||||
{
|
||||
lblCredentialsDomain.Enabled = radCredentialsCustom.Checked;
|
||||
txtCredentialsDomain.Enabled = radCredentialsCustom.Checked;
|
||||
}
|
||||
|
||||
if (!_RegistrySettings.UserViaAPIDefault.IsSet && CommonRegistrySettings.AllowSaveUsernames)
|
||||
{
|
||||
lblCredentialsUserViaAPI.Enabled = radCredentialsCustom.Checked;
|
||||
txtCredentialsUserViaAPI.Enabled = radCredentialsCustom.Checked;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Updates the visibility of the information label indicating whether registry settings are used.
|
||||
/// </summary>
|
||||
private void SetVisibilitySettingsUsedInfo()
|
||||
{
|
||||
lblRegistrySettingsUsedInfo.Visible = ShowRegistrySettingsUsedInfo();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,64 @@
|
||||
<root>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
Reference in New Issue
Block a user