This commit is contained in:
Virtuworks 2015-04-27 10:30:39 -04:00
commit 38ac0aa889
14 changed files with 245 additions and 52 deletions

View file

@ -47,6 +47,14 @@ namespace WebsitePanel.EnterpriseServer
public const string PACKAGE_DISPLAY_SETTINGS = "PackageDisplaySettings";
public const string RDS_SETTINGS = "RdsSettings";
public const string WEBDAV_PORTAL_SETTINGS = "WebdavPortalSettings";
public const string TWILIO_SETTINGS = "TwilioSettings";
//Keys
public const string TWILIO_ACCOUNTSID_KEY = "TwilioAccountSid";
public const string TWILIO_AUTHTOKEN_KEY = "TwilioAuthToken";
public const string TWILIO_PHONEFROM_KEY = "TwilioPhoneFrom";
public const string WEBDAV_PASSWORD_RESET_ENABLED_KEY = "WebdavPswResetEnabled";
// key to access to wpi main & custom feed in wpi settings

View file

@ -2648,21 +2648,23 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution {
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/SendResetUserPasswordEmail", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void SendResetUserPasswordEmail(int itemId, int accountId, string reason, string mailTo) {
public void SendResetUserPasswordEmail(int itemId, int accountId, string reason, string mailTo, bool finalStep) {
this.Invoke("SendResetUserPasswordEmail", new object[] {
itemId,
accountId,
reason,
mailTo});
mailTo,
finalStep});
}
/// <remarks/>
public System.IAsyncResult BeginSendResetUserPasswordEmail(int itemId, int accountId, string reason, string mailTo, System.AsyncCallback callback, object asyncState) {
public System.IAsyncResult BeginSendResetUserPasswordEmail(int itemId, int accountId, string reason, string mailTo, bool finalStep, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("SendResetUserPasswordEmail", new object[] {
itemId,
accountId,
reason,
mailTo}, callback, asyncState);
mailTo,
finalStep}, callback, asyncState);
}
/// <remarks/>
@ -2671,12 +2673,12 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution {
}
/// <remarks/>
public void SendResetUserPasswordEmailAsync(int itemId, int accountId, string reason, string mailTo) {
this.SendResetUserPasswordEmailAsync(itemId, accountId, reason, mailTo, null);
public void SendResetUserPasswordEmailAsync(int itemId, int accountId, string reason, string mailTo, bool finalStep) {
this.SendResetUserPasswordEmailAsync(itemId, accountId, reason, mailTo, finalStep, null);
}
/// <remarks/>
public void SendResetUserPasswordEmailAsync(int itemId, int accountId, string reason, string mailTo, object userState) {
public void SendResetUserPasswordEmailAsync(int itemId, int accountId, string reason, string mailTo, bool finalStep, object userState) {
if ((this.SendResetUserPasswordEmailOperationCompleted == null)) {
this.SendResetUserPasswordEmailOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSendResetUserPasswordEmailOperationCompleted);
}
@ -2684,7 +2686,8 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution {
itemId,
accountId,
reason,
mailTo}, this.SendResetUserPasswordEmailOperationCompleted, userState);
mailTo,
finalStep}, this.SendResetUserPasswordEmailOperationCompleted, userState);
}
private void OnSendResetUserPasswordEmailOperationCompleted(object arg) {

View file

@ -2616,7 +2616,9 @@ namespace WebsitePanel.EnterpriseServer
items["AccountDomain"] = account.PrimaryEmailAddress.Substring(account.PrimaryEmailAddress.IndexOf("@") + 1);
items["DefaultDomain"] = org.DefaultDomain;
var passwordResetUrl = OrganizationController.GenerateUserPasswordResetLink(account.ItemId, account.AccountId);
Guid token;
var passwordResetUrl = OrganizationController.GenerateUserPasswordResetLink(account.ItemId, account.AccountId,out token);
if (!string.IsNullOrEmpty(passwordResetUrl))
{
items["PswResetUrl"] = passwordResetUrl;

View file

@ -1596,9 +1596,12 @@ namespace WebsitePanel.EnterpriseServer
string body = settings["PasswordResetLinkSmsBody"];
var pincode = GeneratePincode();
Guid token;
var items = new Hashtable();
items["passwordResetLink"] = GenerateUserPasswordResetLink(user.ItemId, user.AccountId);
items["passwordResetLink"] = GenerateUserPasswordResetLink(user.ItemId, user.AccountId, out token, pincode);
body = PackageController.EvaluateTemplate(body, items);
@ -1614,6 +1617,8 @@ namespace WebsitePanel.EnterpriseServer
{
throw new Exception(response.RestException.Message);
}
SetAccessTokenResponse(token, pincode);
}
catch (Exception ex)
{
@ -1702,19 +1707,25 @@ namespace WebsitePanel.EnterpriseServer
return random.Next(10000, 99999).ToString(CultureInfo.InvariantCulture);
}
private static TwilioRestClient GetTwilioRestClient()
{
string accountSid = ConfigurationManager.AppSettings["WebsitePanel.Twilio.AccountSid"];
string authToken = ConfigurationManager.AppSettings["WebsitePanel.Twilio.AuthorizationToken"];
return new TwilioRestClient(accountSid, authToken);
}
private static SMSMessage SendSms(string to, string body)
{
var client = GetTwilioRestClient();
SystemSettings settings = SystemController.GetSystemSettingsInternal(SystemSettings.TWILIO_SETTINGS, false);
string phoneFrom = ConfigurationManager.AppSettings["WebsitePanel.Twilio.PhoneFrom"];
if (settings == null)
{
throw new Exception("Twilio settings are not set");
}
string accountSid = settings.GetValueOrDefault(SystemSettings.TWILIO_ACCOUNTSID_KEY, string.Empty);
string authToken = settings.GetValueOrDefault(SystemSettings.TWILIO_AUTHTOKEN_KEY, string.Empty);
string phoneFrom = settings.GetValueOrDefault(SystemSettings.TWILIO_PHONEFROM_KEY, string.Empty);
if (string.IsNullOrEmpty(accountSid) || string.IsNullOrEmpty(accountSid) || string.IsNullOrEmpty(accountSid))
{
throw new Exception("Twilio settings are not set (System settings)");
}
var client = new TwilioRestClient(accountSid, authToken);
return client.SendSmsMessage(phoneFrom, to, body);
}
@ -1726,7 +1737,8 @@ namespace WebsitePanel.EnterpriseServer
/// <param name="accountId">User Id</param>
/// <param name="reason">Reason why reset email is sent.</param>
/// <param name="mailTo">Optional, if null accountID user PrimaryEmailAddress will be used</param>
public static void SendResetUserPasswordEmail(int itemId, int accountId, string reason, string mailTo = null)
/// <param name="finalStep">Url direction</param>
public static void SendResetUserPasswordEmail(int itemId, int accountId, string reason, string mailTo, bool finalStep)
{
// load organization
Organization org = GetOrganization(itemId);
@ -1753,16 +1765,16 @@ namespace WebsitePanel.EnterpriseServer
var logoUrl = generalSettings != null ? generalSettings.OrganizationLogoUrl : string.Empty;
SendUserPasswordEmail(owner, user, reason, mailTo, logoUrl, UserSettings.USER_PASSWORD_RESET_LETTER, "USER_PASSWORD_RESET_LETTER");
SendUserPasswordEmail(owner, user, reason, mailTo, logoUrl, UserSettings.USER_PASSWORD_RESET_LETTER, "USER_PASSWORD_RESET_LETTER", finalStep);
}
public static void SendUserExpirationPasswordEmail(UserInfo owner, OrganizationUser user, string reason,
string mailTo, string logoUrl)
{
SendUserPasswordEmail(owner, user, reason, user.PrimaryEmailAddress, logoUrl, UserSettings.USER_PASSWORD_EXPIRATION_LETTER, "USER_PASSWORD_EXPIRATION_LETTER");
SendUserPasswordEmail(owner, user, reason, user.PrimaryEmailAddress, logoUrl, UserSettings.USER_PASSWORD_EXPIRATION_LETTER, "USER_PASSWORD_EXPIRATION_LETTER", false);
}
public static void SendUserPasswordEmail(UserInfo owner, OrganizationUser user, string reason, string mailTo, string logoUrl, string settingsName, string taskName)
public static void SendUserPasswordEmail(UserInfo owner, OrganizationUser user, string reason, string mailTo, string logoUrl, string settingsName, string taskName, bool finalStep)
{
UserSettings settings = UserController.GetUserSettings(owner.UserId,
settingsName);
@ -1789,14 +1801,23 @@ namespace WebsitePanel.EnterpriseServer
priority = (MailPriority) Enum.Parse(typeof (MailPriority), settings["Priority"], true);
}
Guid token;
string pincode = finalStep ? GeneratePincode() : null;
Hashtable items = new Hashtable();
items["user"] = user;
items["logoUrl"] = logoUrl;
items["passwordResetLink"] = GenerateUserPasswordResetLink(user.ItemId, user.AccountId);
items["passwordResetLink"] = GenerateUserPasswordResetLink(user.ItemId, user.AccountId, out token, pincode);
body = PackageController.EvaluateTemplate(body, items);
if (finalStep)
{
SetAccessTokenResponse(token, pincode);
}
TaskManager.Write("Organization ID : " + user.ItemId);
TaskManager.Write("Account : " + user.DisplayName);
TaskManager.Write("Reason : " + reason);
@ -1837,14 +1858,15 @@ namespace WebsitePanel.EnterpriseServer
return SystemController.GetSystemSettingsInternal(SystemSettings.WEBDAV_PORTAL_SETTINGS, false);
}
public static string GenerateUserPasswordResetLink(int itemId, int accountId)
public static string GenerateUserPasswordResetLink(int itemId, int accountId, out Guid tokenGuid, string pincode = null)
{
string passwordResetUrlFormat = "account/password-reset/step-2";
string passwordResetUrlFormat = string.IsNullOrEmpty(pincode) ? "account/password-reset/step-2" : "account/password-reset/step-final";
var settings = GetWebDavSystemSettings();
if (settings == null || !settings.GetValueOrDefault(SystemSettings.WEBDAV_PASSWORD_RESET_ENABLED_KEY, false) ||!settings.Contains("WebdavPortalUrl"))
{
tokenGuid = new Guid();
return string.Empty;
}
@ -1852,8 +1874,17 @@ namespace WebsitePanel.EnterpriseServer
var token = CreateAccessToken(itemId, accountId, AccessTokenTypes.PasswrodReset);
return webdavPortalUrl.Append(passwordResetUrlFormat)
.Append(token.AccessTokenGuid.ToString("n")).ToString();
tokenGuid = token.AccessTokenGuid;
var resultUrl = webdavPortalUrl.Append(passwordResetUrlFormat)
.Append(token.AccessTokenGuid.ToString("n"));
if (string.IsNullOrEmpty(pincode) == false)
{
resultUrl = resultUrl.Append(pincode);
}
return resultUrl.ToString();
}
private static AccessToken CreateAccessToken(int itemId, int accountId, AccessTokenTypes type)

View file

@ -20,10 +20,6 @@
<add key="WebsitePanel.EnterpriseServer.ServerRequestTimeout" value="3600"/>
<add key="WebsitePanel.AltConnectionString" value="ConnectionString"/>
<add key="WebsitePanel.AltCryptoKey" value="CryptoKey"/>
<add key="WebsitePanel.Twilio.AccountSid" value="1"/>
<add key="WebsitePanel.Twilio.AuthorizationToken" value="2"/>
<add key="WebsitePanel.Twilio.PhoneFrom" value="+15005550006"/>
</appSettings>
<system.web>
<!-- Disable any authentication -->

View file

@ -356,9 +356,9 @@ namespace WebsitePanel.EnterpriseServer
}
[WebMethod]
public void SendResetUserPasswordEmail(int itemId, int accountId, string reason, string mailTo = null)
public void SendResetUserPasswordEmail(int itemId, int accountId, string reason, string mailTo, bool finalStep)
{
OrganizationController.SendResetUserPasswordEmail(itemId, accountId, reason, mailTo);
OrganizationController.SendResetUserPasswordEmail(itemId, accountId, reason, mailTo, finalStep);
}
#endregion

View file

@ -38,14 +38,14 @@ namespace WebsitePanel.WebDavPortal
routes.MapRoute(
name: AccountRouteNames.PasswordResetSendSms,
url: "account/password-reset/step-final/{token}",
url: "account/password-reset/send-new-sms/{token}",
defaults: new { controller = "Account", action = "PasswordResetSendSms" }
);
routes.MapRoute(
name: AccountRouteNames.PasswordResetFinalStep,
url: "account/password-reset/send-new-sms/{token}",
defaults: new { controller = "Account", action = "PasswordResetFinalStep" }
url: "account/password-reset/step-final/{token}/{pincode}",
defaults: new { controller = "Account", action = "PasswordResetFinalStep", pincode = UrlParameter.Optional }
);
routes.MapRoute(

View file

@ -5,6 +5,7 @@ using System.Web.Mvc;
using System.Web.Routing;
using AutoMapper;
using log4net;
using Microsoft.Web.Services3.Addressing;
using WebsitePanel.Providers.HostedSolution;
using WebsitePanel.WebDav.Core.Config;
using WebsitePanel.WebDav.Core.Security.Authentication;
@ -186,7 +187,7 @@ namespace WebsitePanel.WebDavPortal.Controllers
return View(model);
}
WspContext.Services.Organizations.SendResetUserPasswordEmail(exchangeAccount.ItemId, exchangeAccount.AccountId, Resources.Messages.PasswordResetUserReason, exchangeAccount.PrimaryEmailAddress);
WspContext.Services.Organizations.SendResetUserPasswordEmail(exchangeAccount.ItemId, exchangeAccount.AccountId, Resources.Messages.PasswordResetUserReason, exchangeAccount.PrimaryEmailAddress, false);
return View("PasswordResetEmailSent");
}
@ -257,15 +258,16 @@ namespace WebsitePanel.WebDavPortal.Controllers
[HttpGet]
[AllowAnonymous]
public ActionResult PasswordResetFinalStep(Guid token)
public ActionResult PasswordResetFinalStep(Guid token, string pincode)
{
var smsResponse = Session[WebDavAppConfigManager.Instance.SessionKeys.PasswordResetSmsKey] as string;
var result = VerifyPincode(token, pincode);
if (_smsAuthService.VerifyResponse(token, smsResponse) == false)
if (result != null)
{
return RedirectToRoute(AccountRouteNames.PasswordResetSms);
return result;
}
var model = new PasswordEditor();
return View(model);
@ -273,20 +275,18 @@ namespace WebsitePanel.WebDavPortal.Controllers
[HttpPost]
[AllowAnonymous]
public ActionResult PasswordResetFinalStep(Guid token, PasswordEditor model)
public ActionResult PasswordResetFinalStep(Guid token, string pincode, PasswordEditor model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var smsResponse = Session[WebDavAppConfigManager.Instance.SessionKeys.PasswordResetSmsKey] as string;
var result = VerifyPincode(token, pincode);
if (_smsAuthService.VerifyResponse(token, smsResponse) == false)
if (result != null)
{
AddMessage(MessageType.Error, Resources.Messages.IncorrectSmsResponse);
return RedirectToRoute(AccountRouteNames.PasswordResetSms);
return result;
}
var tokenEntity = WspContext.Services.Organizations.GetPasswordresetAccessToken(token);
@ -333,6 +333,34 @@ namespace WebsitePanel.WebDavPortal.Controllers
#region Helpers
/// <summary>
/// Verify pincode, if it's absent - verifying pincode from session
/// </summary>
/// <param name="token">Password reset token</param>
/// <param name="pincode">Pincode to verify if session pincode is absent</param>
private ActionResult VerifyPincode(Guid token, string pincode)
{
var smsResponse = Session[WebDavAppConfigManager.Instance.SessionKeys.PasswordResetSmsKey] as string;
if (string.IsNullOrEmpty(pincode) == false)
{
smsResponse = pincode;
}
if (_smsAuthService.VerifyResponse(token, smsResponse) == false)
{
AddMessage(MessageType.Error, Resources.Messages.IncorrectSmsResponse);
return RedirectToRoute(AccountRouteNames.PasswordResetSms);
}
var tokenEntity = WspContext.Services.Organizations.GetPasswordresetAccessToken(token);
Session[WebDavAppConfigManager.Instance.SessionKeys.ItemId] = tokenEntity.ItemId;
return null;
}
private UserProfile GetUserProfileModel(int itemId, int accountId)
{
var user = WspContext.Services.Organizations.GetUserGeneralSettingsWithExtraData(itemId, accountId);

View file

@ -53,7 +53,7 @@
<add key="ItemIdSessionKey" value="ItemId" />
<add key="UserGroupsKey" value="UserGroups" />
<add key="OwaEditFoldersSession" value="OwaEditFolders" />
<add key="PassswordResetSmsKey" value="PassswordResetSms" />
<add key="PasswordResetSmsKey" value="PasswordResetSms" />
</sessionKeys>
<fileIcons defaultPath="~/Content/Images/other-icon.png" folderPath="~/Content/Images/folder_100x100.png">
<add extension=".txt" path="~/Content/Images/txt-icon.png" />
@ -101,7 +101,6 @@
<add extension=".txt" mimeType="text/plain" isTargetBlank="true" />
<add extension=".pdf" mimeType="application/pdf" isTargetBlank="true" />
</typeOpener>
<twilio accountSid="s" authorizationToken="s" phoneFrom="s"/>
</webDavExplorerConfigurationSettings>
<!--
For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

View file

@ -180,4 +180,16 @@
<data name="locEnablePasswordReset.Text" xml:space="preserve">
<value>Enable password reset:</value>
</data>
<data name="lblAccountSid.Text" xml:space="preserve">
<value>AccountSID</value>
</data>
<data name="lblAuthToken.Text" xml:space="preserve">
<value>AuthToken</value>
</data>
<data name="lblPhoneFrom.Text" xml:space="preserve">
<value>Phone From</value>
</data>
<data name="TwilioSettings.Text" xml:space="preserve">
<value>Twilio</value>
</data>
</root>

View file

@ -39,7 +39,7 @@ namespace WebsitePanel.Portal.ExchangeServer
if (rbtnEmail.Checked)
{
ES.Services.Organizations.SendResetUserPasswordEmail(PanelRequest.ItemID,PanelRequest.AccountID, txtReason.Text, txtEmailAddress.Text);
ES.Services.Organizations.SendResetUserPasswordEmail(PanelRequest.ItemID,PanelRequest.AccountID, txtReason.Text, txtEmailAddress.Text, true);
}
else
{

View file

@ -98,6 +98,24 @@
</tr>
</table>
</asp:Panel>
<wsp:CollapsiblePanel ID="TwilioSettings" runat="server" TargetControlID="PanelTwilioSettings" meta:resourcekey="TwilioSettings" Text="Webdav Portal" />
<asp:Panel ID="PanelTwilioSettings" runat="server" Height="0" style="overflow:hidden;">
<table>
<tr>
<td class="SubHead" style="width:200px;"><asp:Localize ID="lblAccountSid" runat="server" meta:resourcekey="lblAccountSid" />
<td><asp:TextBox runat="server" ID="txtAccountSid" Width="450px" /></td>
</tr>
<tr>
<td class="SubHead" style="width:200px;"><asp:Localize ID="lblAuthToken" runat="server" meta:resourcekey="lblAuthToken" />
<td><asp:TextBox runat="server" ID="txtAuthToken" Width="450px" /></td>
</tr>
<tr>
<td class="SubHead" style="width:200px;"><asp:Localize ID="lblPhoneFrom" runat="server" meta:resourcekey="lblPhoneFrom" />
<td><asp:TextBox runat="server" ID="txtPhoneFrom" Width="450px" /></td>
</tr>
</table>
</asp:Panel>
</div>
<div class="FormFooter">
<asp:Button runat="server" ID="btnSaveSettings" meta:resourcekey="btnSaveSettings"

View file

@ -167,6 +167,17 @@ namespace WebsitePanel.Portal
chkEnablePasswordReset.Checked = Utils.ParseBool(settings[WSP.SystemSettings.WEBDAV_PASSWORD_RESET_ENABLED_KEY], false);
txtWebdavPortalUrl.Text = settings[WEBDAV_PORTAL_URL];
}
// Twilio portal
settings = ES.Services.System.GetSystemSettings(WSP.SystemSettings.TWILIO_SETTINGS);
if (settings != null)
{
txtAccountSid.Text = settings.GetValueOrDefault(WSP.SystemSettings.TWILIO_ACCOUNTSID_KEY, string.Empty);
txtAuthToken.Text = settings.GetValueOrDefault(WSP.SystemSettings.TWILIO_AUTHTOKEN_KEY, string.Empty);
txtPhoneFrom.Text = settings.GetValueOrDefault(WSP.SystemSettings.TWILIO_PHONEFROM_KEY, string.Empty);
}
}
private void SaveSettings()
@ -253,6 +264,19 @@ namespace WebsitePanel.Portal
settings[WSP.SystemSettings.WEBDAV_PASSWORD_RESET_ENABLED_KEY] = chkEnablePasswordReset.Checked.ToString();
result = ES.Services.System.SetSystemSettings(WSP.SystemSettings.WEBDAV_PORTAL_SETTINGS, settings);
if (result < 0)
{
ShowResultMessage(result);
return;
}
// Twilio portal
settings = new WSP.SystemSettings();
settings[WSP.SystemSettings.TWILIO_ACCOUNTSID_KEY] = txtAccountSid.Text;
settings[WSP.SystemSettings.TWILIO_AUTHTOKEN_KEY] = txtAuthToken.Text;
settings[WSP.SystemSettings.TWILIO_PHONEFROM_KEY] = txtPhoneFrom.Text;
result = ES.Services.System.SetSystemSettings(WSP.SystemSettings.TWILIO_SETTINGS, settings);
if (result < 0)
{
ShowResultMessage(result);

View file

@ -282,6 +282,78 @@ namespace WebsitePanel.Portal {
/// </remarks>
protected global::System.Web.UI.WebControls.TextBox txtWebdavPortalUrl;
/// <summary>
/// TwilioSettings control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::WebsitePanel.Portal.CollapsiblePanel TwilioSettings;
/// <summary>
/// PanelTwilioSettings control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Panel PanelTwilioSettings;
/// <summary>
/// lblAccountSid control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Localize lblAccountSid;
/// <summary>
/// txtAccountSid control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.TextBox txtAccountSid;
/// <summary>
/// lblAuthToken control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Localize lblAuthToken;
/// <summary>
/// txtAuthToken control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.TextBox txtAuthToken;
/// <summary>
/// lblPhoneFrom control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Localize lblPhoneFrom;
/// <summary>
/// txtPhoneFrom control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.TextBox txtPhoneFrom;
/// <summary>
/// btnSaveSettings control.
/// </summary>