diff --git a/WebsitePanel/Database/update_db.sql b/WebsitePanel/Database/update_db.sql index 09db0c57..3fb95e49 100644 --- a/WebsitePanel/Database/update_db.sql +++ b/WebsitePanel/Database/update_db.sql @@ -9726,7 +9726,8 @@ CREATE TABLE AccessTokens ExpirationDate DATETIME NOT NULL, AccountID INT NOT NULL , ItemId INT NOT NULL, - TokenType INT NOT NULL + TokenType INT NOT NULL, + SmsResponse varchar(100) ) GO @@ -9771,6 +9772,33 @@ RETURN GO +IF EXISTS (SELECT * FROM SYS.OBJECTS WHERE type = 'P' AND name = 'SetAccessTokenSmsResponse') +DROP PROCEDURE SetAccessTokenSmsResponse +GO +CREATE PROCEDURE [dbo].[SetAccessTokenSmsResponse] +( + @AccessToken UNIQUEIDENTIFIER, + @SmsResponse varchar(100) +) +AS +UPDATE [dbo].[AccessTokens] SET [SmsResponse] = @SmsResponse WHERE [AccessTokenGuid] = @AccessToken +RETURN +GO + +IF EXISTS (SELECT * FROM SYS.OBJECTS WHERE type = 'P' AND name = 'DeleteAccessToken') +DROP PROCEDURE DeleteAccessToken +GO +CREATE PROCEDURE [dbo].[DeleteAccessToken] +( + @AccessToken UNIQUEIDENTIFIER, + @TokenType INT +) +AS +DELETE FROM AccessTokens +WHERE AccessTokenGuid = @AccessToken AND TokenType = @TokenType +GO + + IF EXISTS (SELECT * FROM SYS.OBJECTS WHERE type = 'P' AND name = 'DeleteExpiredAccessTokenTokens') DROP PROCEDURE DeleteExpiredAccessTokenTokens GO @@ -9796,7 +9824,8 @@ SELECT ExpirationDate, AccountID, ItemId, - TokenType + TokenType, + SmsResponse FROM AccessTokens Where AccessTokenGuid = @AccessToken AND ExpirationDate > getdate() AND TokenType = @TokenType GO diff --git a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Base/HostedSolution/AccessToken.cs b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Base/HostedSolution/AccessToken.cs index cbb4bf8b..9556d1c3 100644 --- a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Base/HostedSolution/AccessToken.cs +++ b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Base/HostedSolution/AccessToken.cs @@ -9,6 +9,10 @@ namespace WebsitePanel.EnterpriseServer.Base.HostedSolution public DateTime ExpirationDate { get; set; } public int AccountId { get; set; } public int ItemId { get; set; } - public AccessTokenTypes Type { get; set; } + public AccessTokenTypes TokenType { get; set; } + public string SmsResponse { get; set; } + public bool IsSmsSent { + get { return !string.IsNullOrEmpty(SmsResponse); } + } } } \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Client/OrganizationProxy.cs b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Client/OrganizationProxy.cs index 8cb893f1..92f24a94 100644 --- a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Client/OrganizationProxy.cs +++ b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Client/OrganizationProxy.cs @@ -18,6 +18,7 @@ using WebsitePanel.Providers.Common; using WebsitePanel.Providers.HostedSolution; using WebsitePanel.Providers.ResultObjects; + namespace WebsitePanel.EnterpriseServer.HostedSolution { using System.Xml.Serialization; using System.Web.Services; @@ -36,6 +37,12 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution { [System.Xml.Serialization.XmlIncludeAttribute(typeof(ServiceProviderItem))] public partial class esOrganizations : Microsoft.Web.Services3.WebServicesClientProtocol { + private System.Threading.SendOrPostCallback DeletePasswordresetAccessTokenOperationCompleted; + + private System.Threading.SendOrPostCallback SetAccessTokenResponseOperationCompleted; + + private System.Threading.SendOrPostCallback GetPasswordresetAccessTokenOperationCompleted; + private System.Threading.SendOrPostCallback UpdateOrganizationGeneralSettingsOperationCompleted; private System.Threading.SendOrPostCallback GetOrganizationGeneralSettingsOperationCompleted; @@ -153,6 +160,15 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution { this.Url = "http://localhost:9002/esOrganizations.asmx"; } + /// + public event DeletePasswordresetAccessTokenCompletedEventHandler DeletePasswordresetAccessTokenCompleted; + + /// + public event SetAccessTokenResponseCompletedEventHandler SetAccessTokenResponseCompleted; + + /// + public event GetPasswordresetAccessTokenCompletedEventHandler GetPasswordresetAccessTokenCompleted; + /// public event UpdateOrganizationGeneralSettingsCompletedEventHandler UpdateOrganizationGeneralSettingsCompleted; @@ -321,6 +337,128 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution { /// public event GetSupportServiceLevelCompletedEventHandler GetSupportServiceLevelCompleted; + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/DeletePasswordresetAccessToken", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public void DeletePasswordresetAccessToken(System.Guid accessToken) { + this.Invoke("DeletePasswordresetAccessToken", new object[] { + accessToken}); + } + + /// + public System.IAsyncResult BeginDeletePasswordresetAccessToken(System.Guid accessToken, System.AsyncCallback callback, object asyncState) { + return this.BeginInvoke("DeletePasswordresetAccessToken", new object[] { + accessToken}, callback, asyncState); + } + + /// + public void EndDeletePasswordresetAccessToken(System.IAsyncResult asyncResult) { + this.EndInvoke(asyncResult); + } + + /// + public void DeletePasswordresetAccessTokenAsync(System.Guid accessToken) { + this.DeletePasswordresetAccessTokenAsync(accessToken, null); + } + + /// + public void DeletePasswordresetAccessTokenAsync(System.Guid accessToken, object userState) { + if ((this.DeletePasswordresetAccessTokenOperationCompleted == null)) { + this.DeletePasswordresetAccessTokenOperationCompleted = new System.Threading.SendOrPostCallback(this.OnDeletePasswordresetAccessTokenOperationCompleted); + } + this.InvokeAsync("DeletePasswordresetAccessToken", new object[] { + accessToken}, this.DeletePasswordresetAccessTokenOperationCompleted, userState); + } + + private void OnDeletePasswordresetAccessTokenOperationCompleted(object arg) { + if ((this.DeletePasswordresetAccessTokenCompleted != null)) { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.DeletePasswordresetAccessTokenCompleted(this, new System.ComponentModel.AsyncCompletedEventArgs(invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/SetAccessTokenResponse", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public void SetAccessTokenResponse(System.Guid accessToken, string response) { + this.Invoke("SetAccessTokenResponse", new object[] { + accessToken, + response}); + } + + /// + public System.IAsyncResult BeginSetAccessTokenResponse(System.Guid accessToken, string response, System.AsyncCallback callback, object asyncState) { + return this.BeginInvoke("SetAccessTokenResponse", new object[] { + accessToken, + response}, callback, asyncState); + } + + /// + public void EndSetAccessTokenResponse(System.IAsyncResult asyncResult) { + this.EndInvoke(asyncResult); + } + + /// + public void SetAccessTokenResponseAsync(System.Guid accessToken, string response) { + this.SetAccessTokenResponseAsync(accessToken, response, null); + } + + /// + public void SetAccessTokenResponseAsync(System.Guid accessToken, string response, object userState) { + if ((this.SetAccessTokenResponseOperationCompleted == null)) { + this.SetAccessTokenResponseOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSetAccessTokenResponseOperationCompleted); + } + this.InvokeAsync("SetAccessTokenResponse", new object[] { + accessToken, + response}, this.SetAccessTokenResponseOperationCompleted, userState); + } + + private void OnSetAccessTokenResponseOperationCompleted(object arg) { + if ((this.SetAccessTokenResponseCompleted != null)) { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.SetAccessTokenResponseCompleted(this, new System.ComponentModel.AsyncCompletedEventArgs(invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/GetPasswordresetAccessToken", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public AccessToken GetPasswordresetAccessToken(System.Guid token) { + object[] results = this.Invoke("GetPasswordresetAccessToken", new object[] { + token}); + return ((AccessToken)(results[0])); + } + + /// + public System.IAsyncResult BeginGetPasswordresetAccessToken(System.Guid token, System.AsyncCallback callback, object asyncState) { + return this.BeginInvoke("GetPasswordresetAccessToken", new object[] { + token}, callback, asyncState); + } + + /// + public AccessToken EndGetPasswordresetAccessToken(System.IAsyncResult asyncResult) { + object[] results = this.EndInvoke(asyncResult); + return ((AccessToken)(results[0])); + } + + /// + public void GetPasswordresetAccessTokenAsync(System.Guid token) { + this.GetPasswordresetAccessTokenAsync(token, null); + } + + /// + public void GetPasswordresetAccessTokenAsync(System.Guid token, object userState) { + if ((this.GetPasswordresetAccessTokenOperationCompleted == null)) { + this.GetPasswordresetAccessTokenOperationCompleted = new System.Threading.SendOrPostCallback(this.OnGetPasswordresetAccessTokenOperationCompleted); + } + this.InvokeAsync("GetPasswordresetAccessToken", new object[] { + token}, this.GetPasswordresetAccessTokenOperationCompleted, userState); + } + + private void OnGetPasswordresetAccessTokenOperationCompleted(object arg) { + if ((this.GetPasswordresetAccessTokenCompleted != null)) { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.GetPasswordresetAccessTokenCompleted(this, new GetPasswordresetAccessTokenCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + /// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/UpdateOrganizationGeneralSettings", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] public void UpdateOrganizationGeneralSettings(int itemId, OrganizationGeneralSettings settings) { @@ -3122,6 +3260,40 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution { } } + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")] + public delegate void DeletePasswordresetAccessTokenCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")] + public delegate void SetAccessTokenResponseCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")] + public delegate void GetPasswordresetAccessTokenCompletedEventHandler(object sender, GetPasswordresetAccessTokenCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class GetPasswordresetAccessTokenCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { + + private object[] results; + + internal GetPasswordresetAccessTokenCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) { + this.results = results; + } + + /// + public AccessToken Result { + get { + this.RaiseExceptionIfNecessary(); + return ((AccessToken)(this.results[0])); + } + } + } + /// [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")] public delegate void UpdateOrganizationGeneralSettingsCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e); diff --git a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Code/Data/DataProvider.cs b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Code/Data/DataProvider.cs index a9ee96ac..f1975f10 100644 --- a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Code/Data/DataProvider.cs +++ b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Code/Data/DataProvider.cs @@ -3192,7 +3192,7 @@ namespace WebsitePanel.EnterpriseServer public static int AddAccessToken(AccessToken token) { - return AddAccessToken(token.AccessTokenGuid, token.AccountId, token.ItemId, token.ExpirationDate, token.Type); + return AddAccessToken(token.AccessTokenGuid, token.AccountId, token.ItemId, token.ExpirationDate, token.TokenType); } public static int AddAccessToken(Guid accessToken, int accountId, int itemId, DateTime expirationDate, AccessTokenTypes type) @@ -3216,6 +3216,17 @@ namespace WebsitePanel.EnterpriseServer return Convert.ToInt32(prmId.Value); } + public static void SetAccessTokenResponseMessage(Guid accessToken, string response) + { + SqlHelper.ExecuteNonQuery( + ConnectionString, + CommandType.StoredProcedure, + "SetAccessTokenSmsResponse", + new SqlParameter("@AccessToken", accessToken), + new SqlParameter("@SmsResponse", response) + ); + } + public static void DeleteExpiredAccessTokens() { SqlHelper.ExecuteNonQuery( @@ -3236,6 +3247,17 @@ namespace WebsitePanel.EnterpriseServer ); } + public static void DeleteAccessToken(Guid accessToken, AccessTokenTypes type) + { + SqlHelper.ExecuteNonQuery( + ConnectionString, + CommandType.StoredProcedure, + "DeleteAccessToken", + new SqlParameter("@AccessToken", accessToken), + new SqlParameter("@TokenType", type) + ); + } + public static void UpdateOrganizationSettings(int itemId, string settingsName, string xml) { SqlHelper.ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure, diff --git a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Code/HostedSolution/OrganizationController.cs b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Code/HostedSolution/OrganizationController.cs index 733a4642..c3923871 100644 --- a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Code/HostedSolution/OrganizationController.cs +++ b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer.Code/HostedSolution/OrganizationController.cs @@ -1625,6 +1625,11 @@ namespace WebsitePanel.EnterpriseServer return ObjectUtils.FillObjectFromDataReader(DataProvider.GetAccessTokenByAccessToken(accessToken, type)); } + public static void DeleteAccessToken(Guid accessToken, AccessTokenTypes type) + { + DataProvider.DeleteAccessToken(accessToken, type); + } + public static void DeleteAllExpiredTokens() { DataProvider.DeleteExpiredAccessTokens(); @@ -1632,7 +1637,7 @@ namespace WebsitePanel.EnterpriseServer private static string GenerateUserPasswordResetLink(int itemId, int accountId) { - string passwordResetUrlFormat = "account/password-reset"; + string passwordResetUrlFormat = "account/password-reset/step-2"; var settings = SystemController.GetSystemSettings(SystemSettings.WEBDAV_PORTAL_SETTINGS); @@ -1656,7 +1661,7 @@ namespace WebsitePanel.EnterpriseServer AccessTokenGuid = Guid.NewGuid(), ItemId = itemId, AccountId = accountId, - Type = type, + TokenType = type, ExpirationDate = DateTime.Now.AddHours(12) }; @@ -1665,6 +1670,11 @@ namespace WebsitePanel.EnterpriseServer return token; } + public static void SetAccessTokenResponse(Guid accessToken, string response) + { + DataProvider.SetAccessTokenResponseMessage(accessToken, response); + } + public static void UpdateOrganizationPasswordSettings(int itemId, OrganizationPasswordSettings settings) { TaskManager.StartTask("ORGANIZATION", "UPDATE_PASSWORD_SETTINGS"); diff --git a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer/esOrganizations.asmx.cs b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer/esOrganizations.asmx.cs index e26cd9f3..50080c76 100644 --- a/WebsitePanel/Sources/WebsitePanel.EnterpriseServer/esOrganizations.asmx.cs +++ b/WebsitePanel/Sources/WebsitePanel.EnterpriseServer/esOrganizations.asmx.cs @@ -26,6 +26,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; @@ -47,6 +48,24 @@ namespace WebsitePanel.EnterpriseServer { #region Organizations + [WebMethod] + public void DeletePasswordresetAccessToken(Guid accessToken) + { + OrganizationController.DeleteAccessToken(accessToken, AccessTokenTypes.PasswrodReset); + } + + [WebMethod] + public void SetAccessTokenResponse(Guid accessToken, string response) + { + OrganizationController.SetAccessTokenResponse(accessToken, response); + } + + [WebMethod] + public AccessToken GetPasswordresetAccessToken(Guid token) + { + return OrganizationController.GetAccessToken(token, AccessTokenTypes.PasswrodReset); + } + [WebMethod] public void UpdateOrganizationGeneralSettings(int itemId, OrganizationGeneralSettings settings) { diff --git a/WebsitePanel/Sources/WebsitePanel.Providers.HostedSolution/OrganizationProvider.cs b/WebsitePanel/Sources/WebsitePanel.Providers.HostedSolution/OrganizationProvider.cs index 752509c1..7781540b 100644 --- a/WebsitePanel/Sources/WebsitePanel.Providers.HostedSolution/OrganizationProvider.cs +++ b/WebsitePanel/Sources/WebsitePanel.Providers.HostedSolution/OrganizationProvider.cs @@ -395,11 +395,28 @@ namespace WebsitePanel.Providers.HostedSolution throw new ArgumentNullException("organizationId"); string groupPath = GetGroupPath(organizationId); + string psoName = FormOrganizationPSOName(organizationId); + Runspace runspace = null; + try { + runspace = OpenRunspace(); + + if (FineGrainedPasswordPolicyExist(runspace, psoName)) + { + RemoveFineGrainedPasswordPolicy(runspace, psoName); + } + ActiveDirectoryUtils.DeleteADObject(groupPath); } - catch { /* skip */ } + catch + { + /* skip */ + } + finally + { + CloseRunspace(runspace); + } string path = GetOrganizationPath(organizationId); ActiveDirectoryUtils.DeleteADObject(path, true); @@ -495,45 +512,75 @@ namespace WebsitePanel.Providers.HostedSolution return result; } - var maxPasswordAgeSpan = GetMaxPasswordAge(); + Runspace runspace = null; - var searchRoot = new DirectoryEntry(GetOrganizationPath(organizationId)); - - var search = new DirectorySearcher(searchRoot) + try { - SearchScope = SearchScope.Subtree, - Filter = "(objectClass=user)" - }; + runspace = OpenRunspace(); - search.PropertiesToLoad.Add("pwdLastSet"); - search.PropertiesToLoad.Add("sAMAccountName"); + var psoName = FormOrganizationPSOName(organizationId); - SearchResultCollection searchResults = search.FindAll(); + var maxPasswordAgeSpan = GetMaxPasswordAge(runspace, psoName); - foreach (SearchResult searchResult in searchResults) - { - var pwdLastSetTicks = (long)searchResult.Properties["pwdLastSet"][0]; + var searchRoot = new DirectoryEntry(GetOrganizationPath(organizationId)); - var pwdLastSetDate = DateTime.FromFileTimeUtc(pwdLastSetTicks); - - var expirationDate = pwdLastSetDate.AddDays(maxPasswordAgeSpan.Days); - - if (expirationDate.AddDays(-daysBeforeExpiration) < DateTime.Now) + var search = new DirectorySearcher(searchRoot) { - var user = new OrganizationUser(); + SearchScope = SearchScope.Subtree, + Filter = "(objectClass=user)" + }; - user.PasswordExpirationDateTime = expirationDate; - user.SamAccountName = (string)searchResult.Properties["sAMAccountName"][0]; + search.PropertiesToLoad.Add("pwdLastSet"); + search.PropertiesToLoad.Add("sAMAccountName"); - result.Add(user); + SearchResultCollection searchResults = search.FindAll(); + + foreach (SearchResult searchResult in searchResults) + { + var pwdLastSetTicks = (long) searchResult.Properties["pwdLastSet"][0]; + + var pwdLastSetDate = DateTime.FromFileTimeUtc(pwdLastSetTicks); + + var expirationDate = pwdLastSetDate.AddDays(maxPasswordAgeSpan.Days); + + if (expirationDate.AddDays(-daysBeforeExpiration) < DateTime.Now) + { + var user = new OrganizationUser(); + + user.PasswordExpirationDateTime = expirationDate; + user.SamAccountName = (string) searchResult.Properties["sAMAccountName"][0]; + + result.Add(user); + } } } + catch (Exception) + { + throw; + } + finally + { + CloseRunspace(runspace); + } return result; } - internal TimeSpan GetMaxPasswordAge() + internal TimeSpan GetMaxPasswordAge(Runspace runspace, string psoName) { + if (FineGrainedPasswordPolicyExist(runspace, psoName)) + { + var psoObject = GetFineGrainedPasswordPolicy(runspace, psoName); + + var span = GetPSObjectProperty(psoObject, "MaxPasswordAge") as TimeSpan?; + + if (span != null) + { + return span.Value; + } + } + + using (Domain d = Domain.GetCurrentDomain()) { using (DirectoryEntry domain = d.GetDirectoryEntry()) @@ -563,11 +610,26 @@ namespace WebsitePanel.Providers.HostedSolution Runspace runspace = null; + var psoName = FormOrganizationPSOName(organizationId); + try { runspace = OpenRunspace(); - var gpoId = CreatePolicyIfNotExist(runspace, organizationId, FormOrganizationSettingsGpoName(organizationId)); + if (!FineGrainedPasswordPolicyExist(runspace, psoName)) + { + CreateFineGrainedPasswordPolicy(runspace, organizationId, psoName, settings); + + string groupPath = GetGroupPath(organizationId); + + SetFineGrainedPasswordPolicySubject(runspace, groupPath, psoName); + } + else + { + UpdateFineGrainedPasswordPolicy(runspace, psoName, settings); + + RemoveFineGrainedPasswordPolicy(runspace, psoName); + } } catch (Exception ex) { @@ -581,88 +643,108 @@ namespace WebsitePanel.Providers.HostedSolution } } - private string FormOrganizationSettingsGpoName(string organizationId) + private string FormOrganizationPSOName(string organizationId) { - return string.Format("{0}-settings", organizationId); + return string.Format("{0}-PSO", organizationId); } - private string CreatePolicyIfNotExist(Runspace runspace, string organizationId, string gpoName) + private bool FineGrainedPasswordPolicyExist(Runspace runspace, string psoName) { - string gpoId = GetPolicyId(runspace, gpoName); - - if (string.IsNullOrEmpty(gpoId)) + try { - gpoId = CreateAndLinkPolicy(runspace, gpoName, organizationId); + var cmd = new Command("Get-ADFineGrainedPasswordPolicy"); + cmd.Parameters.Add("Identity", psoName); + + var result = ExecuteShellCommand(runspace, cmd); + } + catch (Exception e) + { + return false; } - return gpoId; + return true; } - private void DeleteGpo(Runspace runspace, string gpoName) + private PSObject GetFineGrainedPasswordPolicy(Runspace runspace, string psoName) { - Command cmd = new Command("Remove-GPO"); - cmd.Parameters.Add("Name", gpoName); + var cmd = new Command("Get-ADFineGrainedPasswordPolicy"); + cmd.Parameters.Add("Identity", psoName); - // Collection result = ExecuteRemoteShellCommand(runspace, PrimaryDomainController, cmd); + return ExecuteShellCommand(runspace, cmd).FirstOrDefault(); } - private string CreateAndLinkPolicy(Runspace runspace, string gpoName, string organizationId) + private void CreateFineGrainedPasswordPolicy(Runspace runspace, string organizationId, string psoName, OrganizationPasswordSettings settings) { - string pathOU = GetOrganizationTargetPath(organizationId); - - //create new gpo - Command cmd = new Command("New-GPO"); - cmd.Parameters.Add("Name", gpoName); - - Collection result = ExecuteShellCommand(runspace, cmd); - - string gpoId = null; - - if (result != null && result.Count > 0) + var cmd = new Command("New-ADFineGrainedPasswordPolicy"); + cmd.Parameters.Add("Name", psoName); + cmd.Parameters.Add("Description", string.Format("The {0} Password Policy", organizationId)); + cmd.Parameters.Add("Precedence", 50); + cmd.Parameters.Add("MinPasswordLength", settings.MinimumLength); + cmd.Parameters.Add("PasswordHistoryCount", settings.EnforcePasswordHistory); + cmd.Parameters.Add("ComplexityEnabled", false); + cmd.Parameters.Add("ReversibleEncryptionEnabled", false); + + if (settings.LockoutSettingsEnabled) { - PSObject gpo = result[0]; - //get gpo id - gpoId = ((Guid) GetPSObjectProperty(gpo, "Id")).ToString("B"); - + cmd.Parameters.Add("LockoutDuration", new TimeSpan(0, settings.AccountLockoutDuration, 0)); + cmd.Parameters.Add("LockoutThreshold", settings.AccountLockoutThreshold); + cmd.Parameters.Add("LockoutObservationWindow", settings.ResetAccountLockoutCounterAfter); } - //create gpo link - cmd = new Command("New-GPLink"); - cmd.Parameters.Add("Name", gpoName); - cmd.Parameters.Add("Target", pathOU); + ExecuteShellCommand(runspace, cmd); + } + + private void SetFineGrainedPasswordPolicySubject(Runspace runspace, string subjectPath, string psoName) + { + var entry = new DirectoryEntry(subjectPath); + + var cmd = new Command("Add-ADFineGrainedPasswordPolicySubject"); + cmd.Parameters.Add("Identity", psoName); + cmd.Parameters.Add("Subjects", entry.Properties[ADAttributes.SAMAccountName].Value.ToString()); ExecuteShellCommand(runspace, cmd); - return gpoId; + cmd = new Command("Set-ADGroup"); + cmd.Parameters.Add("Identity", entry.Properties[ADAttributes.SAMAccountName].Value.ToString()); + cmd.Parameters.Add("GroupScope", "Global"); + + ExecuteShellCommand(runspace, cmd); } - private string GetPolicyId(Runspace runspace, string gpoName) + private void UpdateFineGrainedPasswordPolicy(Runspace runspace, string psoName, OrganizationPasswordSettings settings) { - Runspace runSpace = null; + var cmd = new Command("Set-ADFineGrainedPasswordPolicy"); + cmd.Parameters.Add("Identity", psoName); + cmd.Parameters.Add("MinPasswordLength", settings.MinimumLength); + cmd.Parameters.Add("PasswordHistoryCount", settings.EnforcePasswordHistory); + cmd.Parameters.Add("ComplexityEnabled", false); + cmd.Parameters.Add("ReversibleEncryptionEnabled", false); - string gpoId = null; - - try + if (settings.LockoutSettingsEnabled) { - runSpace = OpenRunspace(); - - Command cmd = new Command("Get-GPO"); - cmd.Parameters.Add("Name", gpoName); - - Collection result = ExecuteShellCommand(runSpace, cmd); - - if (result != null && result.Count > 0) - { - PSObject gpo = result[0]; - gpoId = ((Guid)GetPSObjectProperty(gpo, "Id")).ToString("B"); - } + cmd.Parameters.Add("LockoutDuration", new TimeSpan(0, settings.AccountLockoutDuration, 0)); + cmd.Parameters.Add("LockoutThreshold", settings.AccountLockoutThreshold); + cmd.Parameters.Add("LockoutObservationWindow", settings.ResetAccountLockoutCounterAfter); } - finally + else { - CloseRunspace(runSpace); + cmd.Parameters.Add("LockoutDuration", new TimeSpan(0)); + cmd.Parameters.Add("LockoutThreshold", 0); + cmd.Parameters.Add("LockoutObservationWindow", 0); } - return gpoId; + var result = ExecuteShellCommand(runspace, cmd); + + + var s = GetFineGrainedPasswordPolicy(runspace, psoName); + } + + private void RemoveFineGrainedPasswordPolicy(Runspace runspace, string psoName) + { + var cmd = new Command("Remove-ADFineGrainedPasswordPolicy"); + cmd.Parameters.Add("Identity", psoName); + + ExecuteShellCommand(runspace, cmd); } public PasswordPolicyResult GetPasswordPolicy() @@ -782,7 +864,7 @@ namespace WebsitePanel.Providers.HostedSolution string path = GetUserPath(organizationId, loginName); - OrganizationUser retUser = GetUser(path); + OrganizationUser retUser = GetUser(organizationId, path); HostedSolutionLog.LogEnd("GetUserGeneralSettingsInternal"); return retUser; @@ -835,7 +917,7 @@ namespace WebsitePanel.Providers.HostedSolution user.Properties[ADAttributes.PwdLastSet].Value = userMustChangePassword ? 0 : -1; } - private OrganizationUser GetUser(string path) + private OrganizationUser GetUser(string organizationId, string path) { OrganizationUser retUser = new OrganizationUser(); @@ -871,20 +953,34 @@ namespace WebsitePanel.Providers.HostedSolution retUser.UserPrincipalName = (string)entry.InvokeGet(ADAttributes.UserPrincipalName); retUser.UserMustChangePassword = GetUserMustChangePassword(entry); - retUser.PasswordExpirationDateTime = GetPasswordExpirationDate(entry); + var psoName = FormOrganizationPSOName(organizationId); + + retUser.PasswordExpirationDateTime = GetPasswordExpirationDate(psoName, entry); return retUser; } - private DateTime GetPasswordExpirationDate(DirectoryEntry entry) + private DateTime GetPasswordExpirationDate(string psoName, DirectoryEntry entry) { - var maxPasswordAgeSpan = GetMaxPasswordAge(); + Runspace runspace = null; - var pwdLastSetTicks = ConvertADSLargeIntegerToInt64(entry.Properties[ADAttributes.PwdLastSet].Value); + try + { + runspace = OpenRunspace(); - var pwdLastSetDate = DateTime.FromFileTimeUtc(pwdLastSetTicks); + var maxPasswordAgeSpan = GetMaxPasswordAge(runspace, psoName); + + var pwdLastSetTicks = ConvertADSLargeIntegerToInt64(entry.Properties[ADAttributes.PwdLastSet].Value); + + var pwdLastSetDate = DateTime.FromFileTimeUtc(pwdLastSetTicks); + + return pwdLastSetDate.AddDays(maxPasswordAgeSpan.Days); + } + finally + { + CloseRunspace(runspace); + } - return pwdLastSetDate.AddDays(maxPasswordAgeSpan.Days); } private string GetDomainName(string username) @@ -1253,7 +1349,7 @@ namespace WebsitePanel.Providers.HostedSolution foreach (string userPath in ActiveDirectoryUtils.GetGroupObjects(groupName, "user", organizationEntry)) { - OrganizationUser tmpUser = GetUser(userPath); + OrganizationUser tmpUser = GetUser(organizationId, userPath); members.Add(new ExchangeAccount { diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/Entities/SessionKeysCollection.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/Entities/SessionKeysCollection.cs index 1a45b059..1cbfa2e9 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/Entities/SessionKeysCollection.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/Entities/SessionKeysCollection.cs @@ -65,6 +65,16 @@ namespace WebsitePanel.WebDav.Core.Config.Entities } } + public string PasswordResetSmsKey + { + get + { + SessionKeysElement sessionKey = + _sessionKeys.FirstOrDefault(x => x.Key == SessionKeysElement.PassswordResetSmsKey); + return sessionKey != null ? sessionKey.Value : null; + } + } + public string ResourseRenderCount { get diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/Entities/TwilioParameters.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/Entities/TwilioParameters.cs new file mode 100644 index 00000000..9ce1ffbe --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/Entities/TwilioParameters.cs @@ -0,0 +1,16 @@ +namespace WebsitePanel.WebDav.Core.Config.Entities +{ + public class TwilioParameters: AbstractConfigCollection + { + public string AccountSid { get; private set; } + public string AuthorizationToken { get; private set; } + public string PhoneFrom { get; private set; } + + public TwilioParameters() + { + AccountSid = ConfigSection.Twilio.AccountSid; + AuthorizationToken = ConfigSection.Twilio.AuthorizationToken; + PhoneFrom = ConfigSection.Twilio.PhoneFrom; + } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/IWebDavAppConfig.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/IWebDavAppConfig.cs index 86ce78de..0c72e9c5 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/IWebDavAppConfig.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/IWebDavAppConfig.cs @@ -8,6 +8,7 @@ namespace WebsitePanel.WebDav.Core.Config string ApplicationName { get; } ElementsRendering ElementsRendering { get; } WebsitePanelConstantUserParameters WebsitePanelConstantUserParameters { get; } + TwilioParameters TwilioParameters { get; } SessionKeysCollection SessionKeys { get; } FileIconsDictionary FileIcons { get; } HttpErrorsCollection HttpErrors { get; } diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/SessionKeysElement.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/SessionKeysElement.cs index 76a0e7e8..efeea291 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/SessionKeysElement.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/SessionKeysElement.cs @@ -12,6 +12,7 @@ namespace WebsitePanel.WebDav.Core.Config.WebConfigSections public const string WebDavManagerKey = "WebDavManagerSessionKey"; public const string UserGroupsKey = "UserGroupsKey"; public const string WebDavRootFolderPermissionsKey = "WebDavRootFolderPermissionsKey"; + public const string PassswordResetSmsKey = "PassswordResetSmsKey"; public const string ResourseRenderCountKey = "ResourseRenderCountSessionKey"; public const string ItemIdSessionKey = "ItemId"; public const string OwaEditFoldersSessionKey = "OwaEditFoldersSession"; diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/TwilioElement.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/TwilioElement.cs new file mode 100644 index 00000000..c53856b6 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/TwilioElement.cs @@ -0,0 +1,32 @@ +using System.Configuration; + +namespace WebsitePanel.WebDav.Core.Config.WebConfigSections +{ + public class TwilioElement : ConfigurationElement + { + private const string AccountSidPropName = "accountSid"; + private const string AuthorizationTokenPropName = "authorizationToken"; + private const string PhoneFromPropName = "phoneFrom"; + + [ConfigurationProperty(AccountSidPropName, IsKey = true, IsRequired = true)] + public string AccountSid + { + get { return this[AccountSidPropName].ToString(); } + set { this[AccountSidPropName] = value; } + } + + [ConfigurationProperty(AuthorizationTokenPropName, IsKey = true, IsRequired = true)] + public string AuthorizationToken + { + get { return this[AuthorizationTokenPropName].ToString(); } + set { this[AuthorizationTokenPropName] = value; } + } + + [ConfigurationProperty(PhoneFromPropName, IsKey = true, IsRequired = true)] + public string PhoneFrom + { + get { return this[PhoneFromPropName].ToString(); } + set { this[PhoneFromPropName] = value; } + } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/WebDavExplorerConfigurationSettingsSection.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/WebDavExplorerConfigurationSettingsSection.cs index d471a912..8edbb92f 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/WebDavExplorerConfigurationSettingsSection.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebConfigSections/WebDavExplorerConfigurationSettingsSection.cs @@ -20,6 +20,7 @@ namespace WebsitePanel.WebDavPortal.WebConfigSections private const string OfficeOnlineKey = "officeOnline"; private const string FilesToIgnoreKey = "filesToIgnore"; private const string TypeOpenerKey = "typeOpener"; + private const string TwilioKey = "twilio"; public const string SectionName = "webDavExplorerConfigurationSettings"; @@ -65,6 +66,13 @@ namespace WebsitePanel.WebDavPortal.WebConfigSections set { this[WebsitePanelConstantUserKey] = value; } } + [ConfigurationProperty(TwilioKey, IsRequired = true)] + public TwilioElement Twilio + { + get { return (TwilioElement)this[TwilioKey]; } + set { this[TwilioKey] = value; } + } + [ConfigurationProperty(ElementsRenderingKey, IsRequired = true)] public ElementsRenderingElement ElementsRendering { diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebDavAppConfigManager.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebDavAppConfigManager.cs index 696c1d54..03de83f7 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebDavAppConfigManager.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Config/WebDavAppConfigManager.cs @@ -21,6 +21,7 @@ namespace WebsitePanel.WebDav.Core.Config OwaSupportedBrowsers = new OwaSupportedBrowsersCollection(); FilesToIgnore = new FilesToIgnoreCollection(); FileOpener = new OpenerCollection(); + TwilioParameters = new TwilioParameters(); } public static WebDavAppConfigManager Instance @@ -55,6 +56,7 @@ namespace WebsitePanel.WebDav.Core.Config public ElementsRendering ElementsRendering { get; private set; } public WebsitePanelConstantUserParameters WebsitePanelConstantUserParameters { get; private set; } + public TwilioParameters TwilioParameters { get; private set; } public SessionKeysCollection SessionKeys { get; private set; } public FileIconsDictionary FileIcons { get; private set; } public HttpErrorsCollection HttpErrors { get; private set; } diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Interfaces/Security/ISmsAuthenticationService.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Interfaces/Security/ISmsAuthenticationService.cs new file mode 100644 index 00000000..8eb0b563 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Interfaces/Security/ISmsAuthenticationService.cs @@ -0,0 +1,11 @@ +using System; + +namespace WebsitePanel.WebDav.Core.Interfaces.Security +{ + public interface ISmsAuthenticationService + { + bool VerifyResponse(Guid token, string response); + string SendRequestMessage(string phoneTo); + string GenerateResponse(); + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Interfaces/Services/ISmsDistributionService.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Interfaces/Services/ISmsDistributionService.cs new file mode 100644 index 00000000..2d3185ff --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Interfaces/Services/ISmsDistributionService.cs @@ -0,0 +1,9 @@ +namespace WebsitePanel.WebDav.Core.Interfaces.Services +{ + public interface ISmsDistributionService + { + void SendMessage(string phoneFrom, string phone, string message); + + void SendMessage(string phone, string message); + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Security/Authentication/SmsAuthenticationService.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Security/Authentication/SmsAuthenticationService.cs new file mode 100644 index 00000000..45771b73 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Security/Authentication/SmsAuthenticationService.cs @@ -0,0 +1,46 @@ +using System; +using System.Globalization; +using WebsitePanel.WebDav.Core.Config; +using WebsitePanel.WebDav.Core.Interfaces.Security; +using WebsitePanel.WebDav.Core.Interfaces.Services; + +namespace WebsitePanel.WebDav.Core.Security.Authentication +{ + public class SmsAuthenticationService : ISmsAuthenticationService + { + private ISmsDistributionService _smsService; + + public SmsAuthenticationService(ISmsDistributionService smsService) + { + _smsService = smsService; + } + + public bool VerifyResponse( Guid token, string response) + { + var accessToken = WspContext.Services.Organizations.GetPasswordresetAccessToken(token); + + if (accessToken == null) + { + return false; + } + + return string.Compare(accessToken.SmsResponse, response, StringComparison.InvariantCultureIgnoreCase) == 0; + } + + public string SendRequestMessage(string phoneTo) + { + var response = GenerateResponse(); + + _smsService.SendMessage(WebDavAppConfigManager.Instance.TwilioParameters.PhoneFrom, phoneTo, response); + + return response; + } + + public string GenerateResponse() + { + var random = new Random(Guid.NewGuid().GetHashCode()); + + return random.Next(10000, 99999).ToString(CultureInfo.InvariantCulture); + } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Services/TwillioSmsDistributionService.cs b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Services/TwillioSmsDistributionService.cs new file mode 100644 index 00000000..b764c6e7 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/Services/TwillioSmsDistributionService.cs @@ -0,0 +1,27 @@ +using Twilio; +using WebsitePanel.WebDav.Core.Config; +using WebsitePanel.WebDav.Core.Interfaces.Services; + +namespace WebsitePanel.WebDav.Core.Services +{ + public class TwillioSmsDistributionService : ISmsDistributionService + { + private TwilioRestClient _twilioRestClient { get; set; } + + public TwillioSmsDistributionService() + { + _twilioRestClient = new TwilioRestClient(WebDavAppConfigManager.Instance.TwilioParameters.AccountSid, WebDavAppConfigManager.Instance.TwilioParameters.AuthorizationToken); + } + + + public void SendMessage(string phoneFrom, string phone, string message) + { + _twilioRestClient.SendSmsMessage(phoneFrom, phone, message); + } + + public void SendMessage(string phone, string message) + { + _twilioRestClient.SendSmsMessage(WebDavAppConfigManager.Instance.TwilioParameters.PhoneFrom, phone, message); + } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/WebsitePanel.WebDav.Core.csproj b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/WebsitePanel.WebDav.Core.csproj index 67043bd5..1398a59d 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/WebsitePanel.WebDav.Core.csproj +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/WebsitePanel.WebDav.Core.csproj @@ -46,6 +46,9 @@ False ..\..\Bin\Microsoft.Web.Services3.dll + + ..\packages\RestSharp.105.0.1\lib\net4\RestSharp.dll + @@ -87,6 +90,10 @@ + + ..\packages\Twilio.3.6.29\lib\3.5\Twilio.Api.dll + True + ..\..\Bin\WebsitePanel.EnterpriseServer.Base.dll @@ -108,6 +115,7 @@ + @@ -126,6 +134,7 @@ + @@ -149,6 +158,9 @@ + + + @@ -188,6 +200,7 @@ + diff --git a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/packages.config b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/packages.config index ad6a1af2..7188fe00 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDav.Core/packages.config +++ b/WebsitePanel/Sources/WebsitePanel.WebDav.Core/packages.config @@ -5,4 +5,6 @@ + + \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/App_Start/RouteConfig.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/App_Start/RouteConfig.cs index 93bf1f75..3be2e49d 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/App_Start/RouteConfig.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/App_Start/RouteConfig.cs @@ -18,6 +18,30 @@ namespace WebsitePanel.WebDavPortal defaults: new { controller = "Account", action = "UserProfile" } ); + routes.MapRoute( + name: AccountRouteNames.PasswordResetEmail, + url: "account/password-reset/step-1", + defaults: new { controller = "Account", action = "PasswordResetEmail" } + ); + + routes.MapRoute( + name: AccountRouteNames.PasswordResetSms, + url: "account/password-reset/step-2/{token}", + defaults: new { controller = "Account", action = "PasswordResetSms" } + ); + + routes.MapRoute( + name: AccountRouteNames.PasswordResetSendSms, + url: "account/password-reset/step-final/{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" } + ); + routes.MapRoute( name: AccountRouteNames.PasswordChange, url: "account/profile/password-change", diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/App_Start/Routes/AccountRouteNames.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/App_Start/Routes/AccountRouteNames.cs index 36f4c562..8a3da848 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/App_Start/Routes/AccountRouteNames.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/App_Start/Routes/AccountRouteNames.cs @@ -12,5 +12,9 @@ namespace WebsitePanel.WebDavPortal.UI.Routes public const string UserProfile = "UserProfileRoute"; public const string PasswordChange = "PasswordChangeRoute"; + public const string PasswordResetEmail = "PasswordResetEmailRoute"; + public const string PasswordResetSms = "PasswordResetSmsRoute"; + public const string PasswordResetSendSms = "PasswordResetSendSmsRoute"; + public const string PasswordResetFinalStep = "PasswordResetFinalStepRoute"; } } \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Content/Site.css b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Content/Site.css index 1cbed98d..2173c20a 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Content/Site.css +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Content/Site.css @@ -230,6 +230,9 @@ tr.selected-file { color: white; } +.forgot-your-password-link { + margin-left: 10px; +} .navbar-fixed-top #user-profile { font-size: 18px; diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Controllers/AccountController.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Controllers/AccountController.cs index 091e4899..c533cfc2 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Controllers/AccountController.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Controllers/AccountController.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Net; using System.Web.Mvc; using System.Web.Routing; @@ -7,6 +8,7 @@ using WebsitePanel.Providers.HostedSolution; using WebsitePanel.WebDav.Core.Config; using WebsitePanel.WebDav.Core.Security.Authentication; using WebsitePanel.WebDav.Core.Security.Cryptography; +using WebsitePanel.WebDav.Core.Wsp.Framework; using WebsitePanel.WebDavPortal.CustomAttributes; using WebsitePanel.WebDavPortal.Models; using WebsitePanel.WebDavPortal.Models.Account; @@ -24,16 +26,17 @@ namespace WebsitePanel.WebDavPortal.Controllers { private readonly ICryptography _cryptography; private readonly IAuthenticationService _authenticationService; + private readonly ISmsAuthenticationService _smsAuthService; - public AccountController(ICryptography cryptography, IAuthenticationService authenticationService) + public AccountController(ICryptography cryptography, IAuthenticationService authenticationService, ISmsAuthenticationService smsAuthService) { _cryptography = cryptography; _authenticationService = authenticationService; + _smsAuthService = smsAuthService; } [HttpGet] [AllowAnonymous] - public ActionResult Login() { if (WspContext.User != null && WspContext.User.Identity.IsAuthenticated) @@ -127,6 +130,157 @@ namespace WebsitePanel.WebDavPortal.Controllers return RedirectToRoute(AccountRouteNames.UserProfile); } + [HttpGet] + [AllowAnonymous] + public ActionResult PasswordResetEmail() + { + var model = new PasswordResetEmailModel(); + + return View(model); + } + + [HttpPost] + [AllowAnonymous] + public ActionResult PasswordResetEmail(PasswordResetEmailModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + var exchangeAccount = WspContext.Services.ExchangeServer.GetAccountByAccountNameWithoutItemId(model.Email); + + if (exchangeAccount == null) + { + model.AddMessage(MessageType.Error, Resources.Messages.AccountNotFound); + + return View(model); + } + + WspContext.Services.Organizations.SendResetUserPasswordEmail(exchangeAccount.ItemId, exchangeAccount.AccountId, Resources.Messages.PasswordResetUserReason, exchangeAccount.PrimaryEmailAddress); + + return View("PasswordResetEmailSent"); + } + + + [HttpGet] + [AllowAnonymous] + public ActionResult PasswordResetSms(Guid token) + { + var model = new PasswordResetSmsModel(); + + var accessToken = WspContext.Services.Organizations.GetPasswordresetAccessToken(token); + + model.IsTokenExist = accessToken != null; + + if (model.IsTokenExist == false) + { + model.AddMessage(MessageType.Error, Resources.Messages.IncorrectPasswordResetUrl); + + return View(model); + } + + if (accessToken.IsSmsSent == false) + { + var user = WspContext.Services.Organizations.GetUserGeneralSettings(accessToken.ItemId, accessToken.AccountId); + + var response = _smsAuthService.SendRequestMessage(user.MobilePhone); + WspContext.Services.Organizations.SetAccessTokenResponse(accessToken.AccessTokenGuid, response); + } + + return View(model); + } + + [HttpPost] + [AllowAnonymous] + public ActionResult PasswordResetSms(Guid token, PasswordResetSmsModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + if (_smsAuthService.VerifyResponse(token, model.Sms)) + { + var tokenEntity = WspContext.Services.Organizations.GetPasswordresetAccessToken(token); + + Session[WebDavAppConfigManager.Instance.SessionKeys.PasswordResetSmsKey] = model.Sms; + Session[WebDavAppConfigManager.Instance.SessionKeys.ItemId] = tokenEntity.ItemId; + + return RedirectToRoute(AccountRouteNames.PasswordResetFinalStep); + } + + model.AddMessage(MessageType.Error, Resources.Messages.IncorrectSmsResponse); + + return View(model); + } + + + [HttpGet] + [AllowAnonymous] + public ActionResult PasswordResetFinalStep(Guid token) + { + var smsResponse = Session[WebDavAppConfigManager.Instance.SessionKeys.PasswordResetSmsKey] as string; + + if (_smsAuthService.VerifyResponse(token, smsResponse) == false) + { + return RedirectToRoute(AccountRouteNames.PasswordResetSms); + } + + var model = new PasswordEditor(); + + return View(model); + } + + [HttpPost] + [AllowAnonymous] + public ActionResult PasswordResetFinalStep(Guid token, PasswordEditor model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + var smsResponse = Session[WebDavAppConfigManager.Instance.SessionKeys.PasswordResetSmsKey] as string; + + if (_smsAuthService.VerifyResponse(token, smsResponse) == false) + { + model.AddMessage(MessageType.Error, Resources.Messages.IncorrectSmsResponse); + + return RedirectToRoute(AccountRouteNames.PasswordResetSms); + } + + var tokenEntity = WspContext.Services.Organizations.GetPasswordresetAccessToken(token); + + WspContext.Services.Organizations.SetUserPassword( + tokenEntity.ItemId, tokenEntity.AccountId, + model.NewPassword); + + WspContext.Services.Organizations.DeletePasswordresetAccessToken(token); + + return RedirectToRoute(AccountRouteNames.Login); + } + + [HttpGet] + [AllowAnonymous] + public ActionResult PasswordResetSendSms(Guid token) + { + var accessToken = WspContext.Services.Organizations.GetPasswordresetAccessToken(token); + + if (accessToken == null) + { + return RedirectToRoute(AccountRouteNames.PasswordResetSms); + } + + var user = WspContext.Services.Organizations.GetUserGeneralSettings(accessToken.ItemId, + accessToken.AccountId); + + var response = _smsAuthService.SendRequestMessage(user.MobilePhone); + WspContext.Services.Organizations.SetAccessTokenResponse(accessToken.AccessTokenGuid, response); + + return RedirectToRoute(AccountRouteNames.PasswordResetSms); + } + #region Helpers private UserProfile GetUserProfileModel(int itemId, int accountId) diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/CustomAttributes/OrganizationPasswordPolicyAttribute.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/CustomAttributes/OrganizationPasswordPolicyAttribute.cs index 14cfd4c1..1bd6c7c6 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/CustomAttributes/OrganizationPasswordPolicyAttribute.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/CustomAttributes/OrganizationPasswordPolicyAttribute.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Web; using System.Web.Mvc; using WebsitePanel.Providers.HostedSolution; using WebsitePanel.WebDav.Core; +using WebsitePanel.WebDav.Core.Config; namespace WebsitePanel.WebDavPortal.CustomAttributes { @@ -15,14 +17,25 @@ namespace WebsitePanel.WebDavPortal.CustomAttributes public OrganizationPasswordPolicyAttribute() { - Settings = WspContext.Services.Organizations.GetOrganizationPasswordSettings(WspContext.User.ItemId); + int itemId = -1; + + if (WspContext.User != null) + { + itemId = WspContext.User.ItemId; + } + else if (HttpContext.Current != null && HttpContext.Current.Session[WebDavAppConfigManager.Instance.SessionKeys.ItemId] != null) + { + itemId = (int) HttpContext.Current.Session[WebDavAppConfigManager.Instance.SessionKeys.ItemId]; + } + + + Settings = WspContext.Services.Organizations.GetOrganizationPasswordSettings(itemId); } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { - if (value != null && WspContext.User != null) + if (value != null) { - var resultMessages = new List(); if (Settings != null) diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/DependencyInjection/PortalDependencies.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/DependencyInjection/PortalDependencies.cs index c7661530..ad15cd2b 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/DependencyInjection/PortalDependencies.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/DependencyInjection/PortalDependencies.cs @@ -4,6 +4,7 @@ using WebsitePanel.WebDav.Core.Interfaces.Managers; using WebsitePanel.WebDav.Core.Interfaces.Managers.Users; using WebsitePanel.WebDav.Core.Interfaces.Owa; using WebsitePanel.WebDav.Core.Interfaces.Security; +using WebsitePanel.WebDav.Core.Interfaces.Services; using WebsitePanel.WebDav.Core.Interfaces.Storages; using WebsitePanel.WebDav.Core.Managers; using WebsitePanel.WebDav.Core.Managers.Users; @@ -11,6 +12,7 @@ using WebsitePanel.WebDav.Core.Owa; using WebsitePanel.WebDav.Core.Security.Authentication; using WebsitePanel.WebDav.Core.Security.Authorization; using WebsitePanel.WebDav.Core.Security.Cryptography; +using WebsitePanel.WebDav.Core.Services; using WebsitePanel.WebDav.Core.Storages; using WebsitePanel.WebDavPortal.DependencyInjection.Providers; @@ -31,6 +33,8 @@ namespace WebsitePanel.WebDavPortal.DependencyInjection kernel.Bind().To(); kernel.Bind().To(); kernel.Bind().To(); + kernel.Bind().To(); + kernel.Bind().To(); } } } \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Account/PasswordResetEmailModel.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Account/PasswordResetEmailModel.cs new file mode 100644 index 00000000..2655e419 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Account/PasswordResetEmailModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using WebsitePanel.WebDavPortal.Models.Common; +using WebsitePanel.WebDavPortal.Resources; + +namespace WebsitePanel.WebDavPortal.Models.Account +{ + public class PasswordResetEmailModel : BaseModel + { + [Required] + [Display(ResourceType = typeof(Resources.UI), Name = "Email")] + [EmailAddress(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = "EmailInvalid",ErrorMessage = null)] + public string Email { get; set; } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Account/PasswordResetSmsModel.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Account/PasswordResetSmsModel.cs new file mode 100644 index 00000000..0a0e2715 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Account/PasswordResetSmsModel.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; +using WebsitePanel.WebDavPortal.Models.Common; + +namespace WebsitePanel.WebDavPortal.Models.Account +{ + public class PasswordResetSmsModel : BaseModel + { + [Required] + public string Sms { get; set; } + public bool IsTokenExist { get; set; } + } +} \ No newline at end of file diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Common/EditorTemplates/PasswordEditor.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Common/EditorTemplates/PasswordEditor.cs index 03b30a43..9ef55cc6 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Common/EditorTemplates/PasswordEditor.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Models/Common/EditorTemplates/PasswordEditor.cs @@ -4,7 +4,7 @@ using WebsitePanel.WebDavPortal.CustomAttributes; namespace WebsitePanel.WebDavPortal.Models.Common.EditorTemplates { - public class PasswordEditor + public class PasswordEditor : BaseModel { [Display(ResourceType = typeof(Resources.UI), Name = "NewPassword")] diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/Messages.Designer.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/Messages.Designer.cs index af62e425..04c01d13 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/Messages.Designer.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/Messages.Designer.cs @@ -60,6 +60,15 @@ namespace WebsitePanel.WebDavPortal.Resources { } } + /// + /// Looks up a localized string similar to Account was not found. + /// + public static string AccountNotFound { + get { + return ResourceManager.GetString("AccountNotFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to Email is invalid. /// @@ -69,6 +78,24 @@ namespace WebsitePanel.WebDavPortal.Resources { } } + /// + /// Looks up a localized string similar to Your password could not be reset. The url you followed is either incorrect, has been used or has expired.. + /// + public static string IncorrectPasswordResetUrl { + get { + return ResourceManager.GetString("IncorrectPasswordResetUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Incorrect text message (SMS) response.. + /// + public static string IncorrectSmsResponse { + get { + return ResourceManager.GetString("IncorrectSmsResponse", resourceCulture); + } + } + /// /// Looks up a localized string similar to Old password is not correct. /// @@ -114,6 +141,33 @@ namespace WebsitePanel.WebDavPortal.Resources { } } + /// + /// Looks up a localized string similar to A message was sent to your email address. Please check your email for further instructions.. + /// + public static string PasswordResetEmailSent { + get { + return ResourceManager.GetString("PasswordResetEmailSent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Text message (SMS) was sent to the phone number associated to your account. If you didn't recieve text message (SMS) click {0} for new text message (SMS).. + /// + public static string PasswordResetSmsHintFormat { + get { + return ResourceManager.GetString("PasswordResetSmsHintFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Webdav portal user request.. + /// + public static string PasswordResetUserReason { + get { + return ResourceManager.GetString("PasswordResetUserReason", resourceCulture); + } + } + /// /// Looks up a localized string similar to Password should contain at least {0} non-alphanumeric symbols. /// diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/Messages.resx b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/Messages.resx index 88beb538..2ebec533 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/Messages.resx +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/Messages.resx @@ -117,9 +117,18 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Account was not found + Email is invalid + + Your password could not be reset. The url you followed is either incorrect, has been used or has expired. + + + Incorrect text message (SMS) response. + Old password is not correct @@ -135,6 +144,15 @@ Password should contain at least {0} numbers + + A message was sent to your email address. Please check your email for further instructions. + + + Text message (SMS) was sent to the phone number associated to your account. If you didn't recieve text message (SMS) click {0} for new text message (SMS). + + + Webdav portal user request. + Password should contain at least {0} non-alphanumeric symbols diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/UI.Designer.cs b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/UI.Designer.cs index a88478a0..d189d450 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/UI.Designer.cs +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/UI.Designer.cs @@ -357,6 +357,15 @@ namespace WebsitePanel.WebDavPortal.Resources { } } + /// + /// Looks up a localized string similar to Forgot your password?. + /// + public static string ForgotYourPassword { + get { + return ResourceManager.GetString("ForgotYourPassword", resourceCulture); + } + } + /// /// Looks up a localized string similar to General Information. /// @@ -528,6 +537,15 @@ namespace WebsitePanel.WebDavPortal.Resources { } } + /// + /// Looks up a localized string similar to Next. + /// + public static string Next { + get { + return ResourceManager.GetString("Next", resourceCulture); + } + } + /// /// Looks up a localized string similar to No files are selected.. /// @@ -600,6 +618,15 @@ namespace WebsitePanel.WebDavPortal.Resources { } } + /// + /// Looks up a localized string similar to Password reset. + /// + public static string PasswordReset { + get { + return ResourceManager.GetString("PasswordReset", resourceCulture); + } + } + /// /// Looks up a localized string similar to PB. /// @@ -717,6 +744,15 @@ namespace WebsitePanel.WebDavPortal.Resources { } } + /// + /// Looks up a localized string similar to Send email. + /// + public static string SendEmail { + get { + return ResourceManager.GetString("SendEmail", resourceCulture); + } + } + /// /// Looks up a localized string similar to Size. /// @@ -726,6 +762,15 @@ namespace WebsitePanel.WebDavPortal.Resources { } } + /// + /// Looks up a localized string similar to Sms. + /// + public static string Sms { + get { + return ResourceManager.GetString("Sms", resourceCulture); + } + } + /// /// Looks up a localized string similar to State/Province. /// diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/UI.resx b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/UI.resx index deda00a1..2b5cbd35 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/UI.resx +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Resources/UI.resx @@ -216,6 +216,9 @@ First Name + + Forgot your password? + General Information @@ -273,6 +276,9 @@ Confirm password + + Next + No files are selected. @@ -297,6 +303,9 @@ Will expire on {0}. If you want to change password then please click {1}. + + Password reset + PB @@ -336,9 +345,15 @@ Select files to upload + + Send email + Size + + Sms + State/Province diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Scripts/_references.js b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Scripts/_references.js index 7a771e40..2a5be867 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Scripts/_references.js +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Scripts/_references.js @@ -1,8 +1,9 @@ -/// -/// +/// /// /// +/// /// +/// /// /// /// @@ -10,5 +11,36 @@ /// /// /// +/// +/// +/// /// /// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/Login.cshtml b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/Login.cshtml index c7c827b8..b5d506d8 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/Login.cshtml +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/Login.cshtml @@ -1,4 +1,6 @@ -@model WebsitePanel.WebDavPortal.Models.AccountModel +@using WebsitePanel.WebDavPortal.Resources +@using WebsitePanel.WebDavPortal.UI.Routes +@model WebsitePanel.WebDavPortal.Models.AccountModel @{ ViewBag.Title = "Login"; } @@ -30,6 +32,7 @@ diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetEmail.cshtml b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetEmail.cshtml new file mode 100644 index 00000000..96052e57 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetEmail.cshtml @@ -0,0 +1,31 @@ +@using WebsitePanel.WebDavPortal.Resources +@using WebsitePanel.WebDavPortal.UI.Routes +@model WebsitePanel.WebDavPortal.Models.Account.PasswordResetEmailModel + +@{ + Layout = "~/Views/Shared/_Layout.cshtml"; +} + + +
+ @using (Html.BeginRouteForm(AccountRouteNames.PasswordResetEmail, FormMethod.Post, new { @class = "form-horizontal user-password-reset-email bs-val-styles col-lg-10 col-lg-offset-3", id = "user-password-reset" })) + { +
+

@UI.PasswordReset

+
+
+ +
+ @Html.TextBoxFor(x => x.Email, new { @class = "form-control", placeholder = UI.Email }) + @Html.ValidationMessageFor(x => x.Email) +
+
+ +
+
+ +
+
+ } +
+ diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetEmailSent.cshtml b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetEmailSent.cshtml new file mode 100644 index 00000000..7455f688 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetEmailSent.cshtml @@ -0,0 +1,14 @@ +@using WebsitePanel.WebDavPortal.Resources + +@{ + Layout = "~/Views/Shared/_Layout.cshtml"; +} + + +
+
+

@UI.PasswordReset

+

@Messages.PasswordResetEmailSent

+
+
+ diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetFinalStep.cshtml b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetFinalStep.cshtml new file mode 100644 index 00000000..11fbfbfc --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetFinalStep.cshtml @@ -0,0 +1,27 @@ +@{ + Layout = "~/Views/Shared/_Layout.cshtml"; +} +@using WebsitePanel.WebDavPortal.Resources +@using WebsitePanel.WebDavPortal.UI.Routes +@model WebsitePanel.WebDavPortal.Models.Common.EditorTemplates.PasswordEditor + +
+ @using (Html.BeginRouteForm(AccountRouteNames.PasswordResetFinalStep, FormMethod.Post, new { @class = "form-horizontal user-password-reset-final-step bs-val-styles col-lg-9 col-lg-offset-3", id = "user-password-reset" })) + { + +
+

@UI.PasswordReset

+
+ + @Html.EditorFor(x=>x) + +
+
+ +
+
+ } +
+ + + diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetSms.cshtml b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetSms.cshtml new file mode 100644 index 00000000..08523c08 --- /dev/null +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Account/PasswordResetSms.cshtml @@ -0,0 +1,38 @@ +@using WebsitePanel.WebDavPortal.Resources +@using WebsitePanel.WebDavPortal.UI.Routes +@model WebsitePanel.WebDavPortal.Models.Account.PasswordResetSmsModel + +@{ + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +@if (Model.IsTokenExist) +{ +
+ @using (Html.BeginRouteForm(AccountRouteNames.PasswordResetSms, FormMethod.Post, new {@class = "form-horizontal user-password-reset-sms bs-val-styles col-lg-9 col-lg-offset-3", id = "user-password-reset"})) + { + @Html.HiddenFor(x=>x.IsTokenExist) + +
+

@UI.PasswordReset

+
+
+ @Html.Raw(string.Format(Messages.PasswordResetSmsHintFormat, Html.RouteLink(UI.Here.ToLowerInvariant(), AccountRouteNames.PasswordResetSendSms))) +
+
+ +
+ @Html.TextBoxFor(x => x.Sms, new {@class = "form-control", placeholder = UI.Sms}) + @Html.ValidationMessageFor(x => x.Sms) +
+
+ +
+
+ +
+
+ } +
+} + diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Shared/_Layout.cshtml b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Shared/_Layout.cshtml index 25cc02b3..725be072 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Shared/_Layout.cshtml +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Views/Shared/_Layout.cshtml @@ -43,7 +43,8 @@
-
+
+ @RenderBody()
diff --git a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Web.config b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Web.config index b33b3e60..d186e35d 100644 --- a/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Web.config +++ b/WebsitePanel/Sources/WebsitePanel.WebDavPortal/Web.config @@ -52,7 +52,8 @@ - + + @@ -86,12 +87,12 @@ - - - - - - + + + + + + @@ -100,6 +101,7 @@ +