webdav portal password reset added

This commit is contained in:
vfedosevich 2015-04-14 00:40:11 -07:00
parent 4bae47e17f
commit 599e9a8865
48 changed files with 1163 additions and 117 deletions

View file

@ -9726,7 +9726,8 @@ CREATE TABLE AccessTokens
ExpirationDate DATETIME NOT NULL, ExpirationDate DATETIME NOT NULL,
AccountID INT NOT NULL , AccountID INT NOT NULL ,
ItemId INT NOT NULL, ItemId INT NOT NULL,
TokenType INT NOT NULL TokenType INT NOT NULL,
SmsResponse varchar(100)
) )
GO GO
@ -9771,6 +9772,33 @@ RETURN
GO 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') IF EXISTS (SELECT * FROM SYS.OBJECTS WHERE type = 'P' AND name = 'DeleteExpiredAccessTokenTokens')
DROP PROCEDURE DeleteExpiredAccessTokenTokens DROP PROCEDURE DeleteExpiredAccessTokenTokens
GO GO
@ -9796,7 +9824,8 @@ SELECT
ExpirationDate, ExpirationDate,
AccountID, AccountID,
ItemId, ItemId,
TokenType TokenType,
SmsResponse
FROM AccessTokens FROM AccessTokens
Where AccessTokenGuid = @AccessToken AND ExpirationDate > getdate() AND TokenType = @TokenType Where AccessTokenGuid = @AccessToken AND ExpirationDate > getdate() AND TokenType = @TokenType
GO GO

View file

@ -9,6 +9,10 @@ namespace WebsitePanel.EnterpriseServer.Base.HostedSolution
public DateTime ExpirationDate { get; set; } public DateTime ExpirationDate { get; set; }
public int AccountId { get; set; } public int AccountId { get; set; }
public int ItemId { 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); }
}
} }
} }

View file

@ -18,6 +18,7 @@ using WebsitePanel.Providers.Common;
using WebsitePanel.Providers.HostedSolution; using WebsitePanel.Providers.HostedSolution;
using WebsitePanel.Providers.ResultObjects; using WebsitePanel.Providers.ResultObjects;
namespace WebsitePanel.EnterpriseServer.HostedSolution { namespace WebsitePanel.EnterpriseServer.HostedSolution {
using System.Xml.Serialization; using System.Xml.Serialization;
using System.Web.Services; using System.Web.Services;
@ -36,6 +37,12 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution {
[System.Xml.Serialization.XmlIncludeAttribute(typeof(ServiceProviderItem))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(ServiceProviderItem))]
public partial class esOrganizations : Microsoft.Web.Services3.WebServicesClientProtocol { 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 UpdateOrganizationGeneralSettingsOperationCompleted;
private System.Threading.SendOrPostCallback GetOrganizationGeneralSettingsOperationCompleted; private System.Threading.SendOrPostCallback GetOrganizationGeneralSettingsOperationCompleted;
@ -153,6 +160,15 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution {
this.Url = "http://localhost:9002/esOrganizations.asmx"; this.Url = "http://localhost:9002/esOrganizations.asmx";
} }
/// <remarks/>
public event DeletePasswordresetAccessTokenCompletedEventHandler DeletePasswordresetAccessTokenCompleted;
/// <remarks/>
public event SetAccessTokenResponseCompletedEventHandler SetAccessTokenResponseCompleted;
/// <remarks/>
public event GetPasswordresetAccessTokenCompletedEventHandler GetPasswordresetAccessTokenCompleted;
/// <remarks/> /// <remarks/>
public event UpdateOrganizationGeneralSettingsCompletedEventHandler UpdateOrganizationGeneralSettingsCompleted; public event UpdateOrganizationGeneralSettingsCompletedEventHandler UpdateOrganizationGeneralSettingsCompleted;
@ -321,6 +337,128 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution {
/// <remarks/> /// <remarks/>
public event GetSupportServiceLevelCompletedEventHandler GetSupportServiceLevelCompleted; public event GetSupportServiceLevelCompletedEventHandler GetSupportServiceLevelCompleted;
/// <remarks/>
[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});
}
/// <remarks/>
public System.IAsyncResult BeginDeletePasswordresetAccessToken(System.Guid accessToken, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("DeletePasswordresetAccessToken", new object[] {
accessToken}, callback, asyncState);
}
/// <remarks/>
public void EndDeletePasswordresetAccessToken(System.IAsyncResult asyncResult) {
this.EndInvoke(asyncResult);
}
/// <remarks/>
public void DeletePasswordresetAccessTokenAsync(System.Guid accessToken) {
this.DeletePasswordresetAccessTokenAsync(accessToken, null);
}
/// <remarks/>
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));
}
}
/// <remarks/>
[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});
}
/// <remarks/>
public System.IAsyncResult BeginSetAccessTokenResponse(System.Guid accessToken, string response, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("SetAccessTokenResponse", new object[] {
accessToken,
response}, callback, asyncState);
}
/// <remarks/>
public void EndSetAccessTokenResponse(System.IAsyncResult asyncResult) {
this.EndInvoke(asyncResult);
}
/// <remarks/>
public void SetAccessTokenResponseAsync(System.Guid accessToken, string response) {
this.SetAccessTokenResponseAsync(accessToken, response, null);
}
/// <remarks/>
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));
}
}
/// <remarks/>
[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]));
}
/// <remarks/>
public System.IAsyncResult BeginGetPasswordresetAccessToken(System.Guid token, System.AsyncCallback callback, object asyncState) {
return this.BeginInvoke("GetPasswordresetAccessToken", new object[] {
token}, callback, asyncState);
}
/// <remarks/>
public AccessToken EndGetPasswordresetAccessToken(System.IAsyncResult asyncResult) {
object[] results = this.EndInvoke(asyncResult);
return ((AccessToken)(results[0]));
}
/// <remarks/>
public void GetPasswordresetAccessTokenAsync(System.Guid token) {
this.GetPasswordresetAccessTokenAsync(token, null);
}
/// <remarks/>
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));
}
}
/// <remarks/> /// <remarks/>
[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)] [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) { public void UpdateOrganizationGeneralSettings(int itemId, OrganizationGeneralSettings settings) {
@ -3122,6 +3260,40 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution {
} }
} }
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
public delegate void DeletePasswordresetAccessTokenCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e);
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
public delegate void SetAccessTokenResponseCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e);
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
public delegate void GetPasswordresetAccessTokenCompletedEventHandler(object sender, GetPasswordresetAccessTokenCompletedEventArgs e);
/// <remarks/>
[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;
}
/// <remarks/>
public AccessToken Result {
get {
this.RaiseExceptionIfNecessary();
return ((AccessToken)(this.results[0]));
}
}
}
/// <remarks/> /// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")] [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
public delegate void UpdateOrganizationGeneralSettingsCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e); public delegate void UpdateOrganizationGeneralSettingsCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e);

View file

@ -3192,7 +3192,7 @@ namespace WebsitePanel.EnterpriseServer
public static int AddAccessToken(AccessToken token) 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) 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); 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() public static void DeleteExpiredAccessTokens()
{ {
SqlHelper.ExecuteNonQuery( 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) public static void UpdateOrganizationSettings(int itemId, string settingsName, string xml)
{ {
SqlHelper.ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure, SqlHelper.ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure,

View file

@ -1625,6 +1625,11 @@ namespace WebsitePanel.EnterpriseServer
return ObjectUtils.FillObjectFromDataReader<AccessToken>(DataProvider.GetAccessTokenByAccessToken(accessToken, type)); return ObjectUtils.FillObjectFromDataReader<AccessToken>(DataProvider.GetAccessTokenByAccessToken(accessToken, type));
} }
public static void DeleteAccessToken(Guid accessToken, AccessTokenTypes type)
{
DataProvider.DeleteAccessToken(accessToken, type);
}
public static void DeleteAllExpiredTokens() public static void DeleteAllExpiredTokens()
{ {
DataProvider.DeleteExpiredAccessTokens(); DataProvider.DeleteExpiredAccessTokens();
@ -1632,7 +1637,7 @@ namespace WebsitePanel.EnterpriseServer
private static string GenerateUserPasswordResetLink(int itemId, int accountId) 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); var settings = SystemController.GetSystemSettings(SystemSettings.WEBDAV_PORTAL_SETTINGS);
@ -1656,7 +1661,7 @@ namespace WebsitePanel.EnterpriseServer
AccessTokenGuid = Guid.NewGuid(), AccessTokenGuid = Guid.NewGuid(),
ItemId = itemId, ItemId = itemId,
AccountId = accountId, AccountId = accountId,
Type = type, TokenType = type,
ExpirationDate = DateTime.Now.AddHours(12) ExpirationDate = DateTime.Now.AddHours(12)
}; };
@ -1665,6 +1670,11 @@ namespace WebsitePanel.EnterpriseServer
return token; return token;
} }
public static void SetAccessTokenResponse(Guid accessToken, string response)
{
DataProvider.SetAccessTokenResponseMessage(accessToken, response);
}
public static void UpdateOrganizationPasswordSettings(int itemId, OrganizationPasswordSettings settings) public static void UpdateOrganizationPasswordSettings(int itemId, OrganizationPasswordSettings settings)
{ {
TaskManager.StartTask("ORGANIZATION", "UPDATE_PASSWORD_SETTINGS"); TaskManager.StartTask("ORGANIZATION", "UPDATE_PASSWORD_SETTINGS");

View file

@ -26,6 +26,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Data; using System.Data;
@ -47,6 +48,24 @@ namespace WebsitePanel.EnterpriseServer
{ {
#region Organizations #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] [WebMethod]
public void UpdateOrganizationGeneralSettings(int itemId, OrganizationGeneralSettings settings) public void UpdateOrganizationGeneralSettings(int itemId, OrganizationGeneralSettings settings)
{ {

View file

@ -395,11 +395,28 @@ namespace WebsitePanel.Providers.HostedSolution
throw new ArgumentNullException("organizationId"); throw new ArgumentNullException("organizationId");
string groupPath = GetGroupPath(organizationId); string groupPath = GetGroupPath(organizationId);
string psoName = FormOrganizationPSOName(organizationId);
Runspace runspace = null;
try try
{ {
runspace = OpenRunspace();
if (FineGrainedPasswordPolicyExist(runspace, psoName))
{
RemoveFineGrainedPasswordPolicy(runspace, psoName);
}
ActiveDirectoryUtils.DeleteADObject(groupPath); ActiveDirectoryUtils.DeleteADObject(groupPath);
} }
catch { /* skip */ } catch
{
/* skip */
}
finally
{
CloseRunspace(runspace);
}
string path = GetOrganizationPath(organizationId); string path = GetOrganizationPath(organizationId);
ActiveDirectoryUtils.DeleteADObject(path, true); ActiveDirectoryUtils.DeleteADObject(path, true);
@ -495,45 +512,75 @@ namespace WebsitePanel.Providers.HostedSolution
return result; return result;
} }
var maxPasswordAgeSpan = GetMaxPasswordAge(); Runspace runspace = null;
var searchRoot = new DirectoryEntry(GetOrganizationPath(organizationId)); try
var search = new DirectorySearcher(searchRoot)
{ {
SearchScope = SearchScope.Subtree, runspace = OpenRunspace();
Filter = "(objectClass=user)"
};
search.PropertiesToLoad.Add("pwdLastSet"); var psoName = FormOrganizationPSOName(organizationId);
search.PropertiesToLoad.Add("sAMAccountName");
SearchResultCollection searchResults = search.FindAll(); var maxPasswordAgeSpan = GetMaxPasswordAge(runspace, psoName);
foreach (SearchResult searchResult in searchResults) var searchRoot = new DirectoryEntry(GetOrganizationPath(organizationId));
{
var pwdLastSetTicks = (long)searchResult.Properties["pwdLastSet"][0];
var pwdLastSetDate = DateTime.FromFileTimeUtc(pwdLastSetTicks); var search = new DirectorySearcher(searchRoot)
var expirationDate = pwdLastSetDate.AddDays(maxPasswordAgeSpan.Days);
if (expirationDate.AddDays(-daysBeforeExpiration) < DateTime.Now)
{ {
var user = new OrganizationUser(); SearchScope = SearchScope.Subtree,
Filter = "(objectClass=user)"
};
user.PasswordExpirationDateTime = expirationDate; search.PropertiesToLoad.Add("pwdLastSet");
user.SamAccountName = (string)searchResult.Properties["sAMAccountName"][0]; 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; 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 (Domain d = Domain.GetCurrentDomain())
{ {
using (DirectoryEntry domain = d.GetDirectoryEntry()) using (DirectoryEntry domain = d.GetDirectoryEntry())
@ -563,11 +610,26 @@ namespace WebsitePanel.Providers.HostedSolution
Runspace runspace = null; Runspace runspace = null;
var psoName = FormOrganizationPSOName(organizationId);
try try
{ {
runspace = OpenRunspace(); 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) 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); try
if (string.IsNullOrEmpty(gpoId))
{ {
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"); var cmd = new Command("Get-ADFineGrainedPasswordPolicy");
cmd.Parameters.Add("Name", gpoName); cmd.Parameters.Add("Identity", psoName);
// Collection<PSObject> 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); 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);
//create new gpo if (settings.LockoutSettingsEnabled)
Command cmd = new Command("New-GPO");
cmd.Parameters.Add("Name", gpoName);
Collection<PSObject> result = ExecuteShellCommand(runspace, cmd);
string gpoId = null;
if (result != null && result.Count > 0)
{ {
PSObject gpo = result[0]; cmd.Parameters.Add("LockoutDuration", new TimeSpan(0, settings.AccountLockoutDuration, 0));
//get gpo id cmd.Parameters.Add("LockoutThreshold", settings.AccountLockoutThreshold);
gpoId = ((Guid) GetPSObjectProperty(gpo, "Id")).ToString("B"); cmd.Parameters.Add("LockoutObservationWindow", settings.ResetAccountLockoutCounterAfter);
} }
//create gpo link ExecuteShellCommand(runspace, cmd);
cmd = new Command("New-GPLink"); }
cmd.Parameters.Add("Name", gpoName);
cmd.Parameters.Add("Target", pathOU); 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); 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; if (settings.LockoutSettingsEnabled)
try
{ {
runSpace = OpenRunspace(); cmd.Parameters.Add("LockoutDuration", new TimeSpan(0, settings.AccountLockoutDuration, 0));
cmd.Parameters.Add("LockoutThreshold", settings.AccountLockoutThreshold);
Command cmd = new Command("Get-GPO"); cmd.Parameters.Add("LockoutObservationWindow", settings.ResetAccountLockoutCounterAfter);
cmd.Parameters.Add("Name", gpoName);
Collection<PSObject> result = ExecuteShellCommand(runSpace, cmd);
if (result != null && result.Count > 0)
{
PSObject gpo = result[0];
gpoId = ((Guid)GetPSObjectProperty(gpo, "Id")).ToString("B");
}
} }
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() public PasswordPolicyResult GetPasswordPolicy()
@ -782,7 +864,7 @@ namespace WebsitePanel.Providers.HostedSolution
string path = GetUserPath(organizationId, loginName); string path = GetUserPath(organizationId, loginName);
OrganizationUser retUser = GetUser(path); OrganizationUser retUser = GetUser(organizationId, path);
HostedSolutionLog.LogEnd("GetUserGeneralSettingsInternal"); HostedSolutionLog.LogEnd("GetUserGeneralSettingsInternal");
return retUser; return retUser;
@ -835,7 +917,7 @@ namespace WebsitePanel.Providers.HostedSolution
user.Properties[ADAttributes.PwdLastSet].Value = userMustChangePassword ? 0 : -1; user.Properties[ADAttributes.PwdLastSet].Value = userMustChangePassword ? 0 : -1;
} }
private OrganizationUser GetUser(string path) private OrganizationUser GetUser(string organizationId, string path)
{ {
OrganizationUser retUser = new OrganizationUser(); OrganizationUser retUser = new OrganizationUser();
@ -871,20 +953,34 @@ namespace WebsitePanel.Providers.HostedSolution
retUser.UserPrincipalName = (string)entry.InvokeGet(ADAttributes.UserPrincipalName); retUser.UserPrincipalName = (string)entry.InvokeGet(ADAttributes.UserPrincipalName);
retUser.UserMustChangePassword = GetUserMustChangePassword(entry); retUser.UserMustChangePassword = GetUserMustChangePassword(entry);
retUser.PasswordExpirationDateTime = GetPasswordExpirationDate(entry); var psoName = FormOrganizationPSOName(organizationId);
retUser.PasswordExpirationDateTime = GetPasswordExpirationDate(psoName, entry);
return retUser; 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) private string GetDomainName(string username)
@ -1253,7 +1349,7 @@ namespace WebsitePanel.Providers.HostedSolution
foreach (string userPath in ActiveDirectoryUtils.GetGroupObjects(groupName, "user", organizationEntry)) foreach (string userPath in ActiveDirectoryUtils.GetGroupObjects(groupName, "user", organizationEntry))
{ {
OrganizationUser tmpUser = GetUser(userPath); OrganizationUser tmpUser = GetUser(organizationId, userPath);
members.Add(new ExchangeAccount members.Add(new ExchangeAccount
{ {

View file

@ -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 public string ResourseRenderCount
{ {
get get

View file

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

View file

@ -8,6 +8,7 @@ namespace WebsitePanel.WebDav.Core.Config
string ApplicationName { get; } string ApplicationName { get; }
ElementsRendering ElementsRendering { get; } ElementsRendering ElementsRendering { get; }
WebsitePanelConstantUserParameters WebsitePanelConstantUserParameters { get; } WebsitePanelConstantUserParameters WebsitePanelConstantUserParameters { get; }
TwilioParameters TwilioParameters { get; }
SessionKeysCollection SessionKeys { get; } SessionKeysCollection SessionKeys { get; }
FileIconsDictionary FileIcons { get; } FileIconsDictionary FileIcons { get; }
HttpErrorsCollection HttpErrors { get; } HttpErrorsCollection HttpErrors { get; }

View file

@ -12,6 +12,7 @@ namespace WebsitePanel.WebDav.Core.Config.WebConfigSections
public const string WebDavManagerKey = "WebDavManagerSessionKey"; public const string WebDavManagerKey = "WebDavManagerSessionKey";
public const string UserGroupsKey = "UserGroupsKey"; public const string UserGroupsKey = "UserGroupsKey";
public const string WebDavRootFolderPermissionsKey = "WebDavRootFolderPermissionsKey"; public const string WebDavRootFolderPermissionsKey = "WebDavRootFolderPermissionsKey";
public const string PassswordResetSmsKey = "PassswordResetSmsKey";
public const string ResourseRenderCountKey = "ResourseRenderCountSessionKey"; public const string ResourseRenderCountKey = "ResourseRenderCountSessionKey";
public const string ItemIdSessionKey = "ItemId"; public const string ItemIdSessionKey = "ItemId";
public const string OwaEditFoldersSessionKey = "OwaEditFoldersSession"; public const string OwaEditFoldersSessionKey = "OwaEditFoldersSession";

View file

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

View file

@ -20,6 +20,7 @@ namespace WebsitePanel.WebDavPortal.WebConfigSections
private const string OfficeOnlineKey = "officeOnline"; private const string OfficeOnlineKey = "officeOnline";
private const string FilesToIgnoreKey = "filesToIgnore"; private const string FilesToIgnoreKey = "filesToIgnore";
private const string TypeOpenerKey = "typeOpener"; private const string TypeOpenerKey = "typeOpener";
private const string TwilioKey = "twilio";
public const string SectionName = "webDavExplorerConfigurationSettings"; public const string SectionName = "webDavExplorerConfigurationSettings";
@ -65,6 +66,13 @@ namespace WebsitePanel.WebDavPortal.WebConfigSections
set { this[WebsitePanelConstantUserKey] = value; } set { this[WebsitePanelConstantUserKey] = value; }
} }
[ConfigurationProperty(TwilioKey, IsRequired = true)]
public TwilioElement Twilio
{
get { return (TwilioElement)this[TwilioKey]; }
set { this[TwilioKey] = value; }
}
[ConfigurationProperty(ElementsRenderingKey, IsRequired = true)] [ConfigurationProperty(ElementsRenderingKey, IsRequired = true)]
public ElementsRenderingElement ElementsRendering public ElementsRenderingElement ElementsRendering
{ {

View file

@ -21,6 +21,7 @@ namespace WebsitePanel.WebDav.Core.Config
OwaSupportedBrowsers = new OwaSupportedBrowsersCollection(); OwaSupportedBrowsers = new OwaSupportedBrowsersCollection();
FilesToIgnore = new FilesToIgnoreCollection(); FilesToIgnore = new FilesToIgnoreCollection();
FileOpener = new OpenerCollection(); FileOpener = new OpenerCollection();
TwilioParameters = new TwilioParameters();
} }
public static WebDavAppConfigManager Instance public static WebDavAppConfigManager Instance
@ -55,6 +56,7 @@ namespace WebsitePanel.WebDav.Core.Config
public ElementsRendering ElementsRendering { get; private set; } public ElementsRendering ElementsRendering { get; private set; }
public WebsitePanelConstantUserParameters WebsitePanelConstantUserParameters { get; private set; } public WebsitePanelConstantUserParameters WebsitePanelConstantUserParameters { get; private set; }
public TwilioParameters TwilioParameters { get; private set; }
public SessionKeysCollection SessionKeys { get; private set; } public SessionKeysCollection SessionKeys { get; private set; }
public FileIconsDictionary FileIcons { get; private set; } public FileIconsDictionary FileIcons { get; private set; }
public HttpErrorsCollection HttpErrors { get; private set; } public HttpErrorsCollection HttpErrors { get; private set; }

View file

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

View file

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

View file

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

View file

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

View file

@ -46,6 +46,9 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Bin\Microsoft.Web.Services3.dll</HintPath> <HintPath>..\..\Bin\Microsoft.Web.Services3.dll</HintPath>
</Reference> </Reference>
<Reference Include="RestSharp">
<HintPath>..\packages\RestSharp.105.0.1\lib\net4\RestSharp.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
@ -87,6 +90,10 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="Twilio.Api">
<HintPath>..\packages\Twilio.3.6.29\lib\3.5\Twilio.Api.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="WebsitePanel.EnterpriseServer.Base"> <Reference Include="WebsitePanel.EnterpriseServer.Base">
<HintPath>..\..\Bin\WebsitePanel.EnterpriseServer.Base.dll</HintPath> <HintPath>..\..\Bin\WebsitePanel.EnterpriseServer.Base.dll</HintPath>
</Reference> </Reference>
@ -108,6 +115,7 @@
<Compile Include="Config\Entities\OwaSupportedBrowsersCollection.cs" /> <Compile Include="Config\Entities\OwaSupportedBrowsersCollection.cs" />
<Compile Include="Config\Entities\SessionKeysCollection.cs" /> <Compile Include="Config\Entities\SessionKeysCollection.cs" />
<Compile Include="Config\Entities\OpenerCollection.cs" /> <Compile Include="Config\Entities\OpenerCollection.cs" />
<Compile Include="Config\Entities\TwilioParameters.cs" />
<Compile Include="Config\Entities\WebsitePanelConstantUserParameters.cs" /> <Compile Include="Config\Entities\WebsitePanelConstantUserParameters.cs" />
<Compile Include="Config\IWebDavAppConfig.cs" /> <Compile Include="Config\IWebDavAppConfig.cs" />
<Compile Include="Config\WebConfigSections\ApplicationNameElement.cs" /> <Compile Include="Config\WebConfigSections\ApplicationNameElement.cs" />
@ -126,6 +134,7 @@
<Compile Include="Config\WebConfigSections\SessionKeysElement.cs" /> <Compile Include="Config\WebConfigSections\SessionKeysElement.cs" />
<Compile Include="Config\WebConfigSections\SessionKeysElementCollection.cs" /> <Compile Include="Config\WebConfigSections\SessionKeysElementCollection.cs" />
<Compile Include="Config\WebConfigSections\OpenerElement.cs" /> <Compile Include="Config\WebConfigSections\OpenerElement.cs" />
<Compile Include="Config\WebConfigSections\TwilioElement.cs" />
<Compile Include="Config\WebConfigSections\UserDomainElement.cs" /> <Compile Include="Config\WebConfigSections\UserDomainElement.cs" />
<Compile Include="Config\WebConfigSections\WebDavExplorerConfigurationSettingsSection.cs" /> <Compile Include="Config\WebConfigSections\WebDavExplorerConfigurationSettingsSection.cs" />
<Compile Include="Config\WebConfigSections\WebdavRootElement.cs" /> <Compile Include="Config\WebConfigSections\WebdavRootElement.cs" />
@ -149,6 +158,9 @@
<Compile Include="IHierarchyItem.cs" /> <Compile Include="IHierarchyItem.cs" />
<Compile Include="IItemContent.cs" /> <Compile Include="IItemContent.cs" />
<Compile Include="Interfaces\Managers\Users\IUserSettingsManager.cs" /> <Compile Include="Interfaces\Managers\Users\IUserSettingsManager.cs" />
<Compile Include="Interfaces\Security\ISmsAuthenticationService.cs" />
<Compile Include="Security\Authentication\SmsAuthenticationService.cs" />
<Compile Include="Interfaces\Services\ISmsDistributionService.cs" />
<Compile Include="Interfaces\Storages\IKeyValueStorage.cs" /> <Compile Include="Interfaces\Storages\IKeyValueStorage.cs" />
<Compile Include="Interfaces\Storages\ITtlStorage.cs" /> <Compile Include="Interfaces\Storages\ITtlStorage.cs" />
<Compile Include="Managers\Users\UserSettingsManager.cs" /> <Compile Include="Managers\Users\UserSettingsManager.cs" />
@ -188,6 +200,7 @@
<Compile Include="Security\Authentication\FormsAuthenticationService.cs" /> <Compile Include="Security\Authentication\FormsAuthenticationService.cs" />
<Compile Include="Security\Authentication\Principals\WspPrincipal.cs" /> <Compile Include="Security\Authentication\Principals\WspPrincipal.cs" />
<Compile Include="Owa\WopiServer.cs" /> <Compile Include="Owa\WopiServer.cs" />
<Compile Include="Services\TwillioSmsDistributionService.cs" />
<Compile Include="Storages\CacheTtlStorage.cs" /> <Compile Include="Storages\CacheTtlStorage.cs" />
<Compile Include="WebDavSession.cs" /> <Compile Include="WebDavSession.cs" />
<Compile Include="WspContext.cs" /> <Compile Include="WspContext.cs" />

View file

@ -5,4 +5,6 @@
<package id="Microsoft.AspNet.Razor" version="3.2.2" targetFramework="net45" /> <package id="Microsoft.AspNet.Razor" version="3.2.2" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="3.2.2" targetFramework="net45" /> <package id="Microsoft.AspNet.WebPages" version="3.2.2" targetFramework="net45" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" /> <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
<package id="RestSharp" version="105.0.1" targetFramework="net45" />
<package id="Twilio" version="3.6.29" targetFramework="net45" />
</packages> </packages>

View file

@ -18,6 +18,30 @@ namespace WebsitePanel.WebDavPortal
defaults: new { controller = "Account", action = "UserProfile" } 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( routes.MapRoute(
name: AccountRouteNames.PasswordChange, name: AccountRouteNames.PasswordChange,
url: "account/profile/password-change", url: "account/profile/password-change",

View file

@ -12,5 +12,9 @@ namespace WebsitePanel.WebDavPortal.UI.Routes
public const string UserProfile = "UserProfileRoute"; public const string UserProfile = "UserProfileRoute";
public const string PasswordChange = "PasswordChangeRoute"; 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";
} }
} }

View file

@ -230,6 +230,9 @@ tr.selected-file {
color: white; color: white;
} }
.forgot-your-password-link {
margin-left: 10px;
}
.navbar-fixed-top #user-profile { .navbar-fixed-top #user-profile {
font-size: 18px; font-size: 18px;

View file

@ -1,4 +1,5 @@
using System.Linq; using System;
using System.Linq;
using System.Net; using System.Net;
using System.Web.Mvc; using System.Web.Mvc;
using System.Web.Routing; using System.Web.Routing;
@ -7,6 +8,7 @@ using WebsitePanel.Providers.HostedSolution;
using WebsitePanel.WebDav.Core.Config; using WebsitePanel.WebDav.Core.Config;
using WebsitePanel.WebDav.Core.Security.Authentication; using WebsitePanel.WebDav.Core.Security.Authentication;
using WebsitePanel.WebDav.Core.Security.Cryptography; using WebsitePanel.WebDav.Core.Security.Cryptography;
using WebsitePanel.WebDav.Core.Wsp.Framework;
using WebsitePanel.WebDavPortal.CustomAttributes; using WebsitePanel.WebDavPortal.CustomAttributes;
using WebsitePanel.WebDavPortal.Models; using WebsitePanel.WebDavPortal.Models;
using WebsitePanel.WebDavPortal.Models.Account; using WebsitePanel.WebDavPortal.Models.Account;
@ -24,16 +26,17 @@ namespace WebsitePanel.WebDavPortal.Controllers
{ {
private readonly ICryptography _cryptography; private readonly ICryptography _cryptography;
private readonly IAuthenticationService _authenticationService; private readonly IAuthenticationService _authenticationService;
private readonly ISmsAuthenticationService _smsAuthService;
public AccountController(ICryptography cryptography, IAuthenticationService authenticationService) public AccountController(ICryptography cryptography, IAuthenticationService authenticationService, ISmsAuthenticationService smsAuthService)
{ {
_cryptography = cryptography; _cryptography = cryptography;
_authenticationService = authenticationService; _authenticationService = authenticationService;
_smsAuthService = smsAuthService;
} }
[HttpGet] [HttpGet]
[AllowAnonymous] [AllowAnonymous]
public ActionResult Login() public ActionResult Login()
{ {
if (WspContext.User != null && WspContext.User.Identity.IsAuthenticated) if (WspContext.User != null && WspContext.User.Identity.IsAuthenticated)
@ -127,6 +130,157 @@ namespace WebsitePanel.WebDavPortal.Controllers
return RedirectToRoute(AccountRouteNames.UserProfile); 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 #region Helpers
private UserProfile GetUserProfileModel(int itemId, int accountId) private UserProfile GetUserProfileModel(int itemId, int accountId)

View file

@ -2,9 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Web;
using System.Web.Mvc; using System.Web.Mvc;
using WebsitePanel.Providers.HostedSolution; using WebsitePanel.Providers.HostedSolution;
using WebsitePanel.WebDav.Core; using WebsitePanel.WebDav.Core;
using WebsitePanel.WebDav.Core.Config;
namespace WebsitePanel.WebDavPortal.CustomAttributes namespace WebsitePanel.WebDavPortal.CustomAttributes
{ {
@ -15,14 +17,25 @@ namespace WebsitePanel.WebDavPortal.CustomAttributes
public OrganizationPasswordPolicyAttribute() 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) protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{ {
if (value != null && WspContext.User != null) if (value != null)
{ {
var resultMessages = new List<string>(); var resultMessages = new List<string>();
if (Settings != null) if (Settings != null)

View file

@ -4,6 +4,7 @@ using WebsitePanel.WebDav.Core.Interfaces.Managers;
using WebsitePanel.WebDav.Core.Interfaces.Managers.Users; using WebsitePanel.WebDav.Core.Interfaces.Managers.Users;
using WebsitePanel.WebDav.Core.Interfaces.Owa; using WebsitePanel.WebDav.Core.Interfaces.Owa;
using WebsitePanel.WebDav.Core.Interfaces.Security; using WebsitePanel.WebDav.Core.Interfaces.Security;
using WebsitePanel.WebDav.Core.Interfaces.Services;
using WebsitePanel.WebDav.Core.Interfaces.Storages; using WebsitePanel.WebDav.Core.Interfaces.Storages;
using WebsitePanel.WebDav.Core.Managers; using WebsitePanel.WebDav.Core.Managers;
using WebsitePanel.WebDav.Core.Managers.Users; 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.Authentication;
using WebsitePanel.WebDav.Core.Security.Authorization; using WebsitePanel.WebDav.Core.Security.Authorization;
using WebsitePanel.WebDav.Core.Security.Cryptography; using WebsitePanel.WebDav.Core.Security.Cryptography;
using WebsitePanel.WebDav.Core.Services;
using WebsitePanel.WebDav.Core.Storages; using WebsitePanel.WebDav.Core.Storages;
using WebsitePanel.WebDavPortal.DependencyInjection.Providers; using WebsitePanel.WebDavPortal.DependencyInjection.Providers;
@ -31,6 +33,8 @@ namespace WebsitePanel.WebDavPortal.DependencyInjection
kernel.Bind<ICobaltManager>().To<CobaltManager>(); kernel.Bind<ICobaltManager>().To<CobaltManager>();
kernel.Bind<ITtlStorage>().To<CacheTtlStorage>(); kernel.Bind<ITtlStorage>().To<CacheTtlStorage>();
kernel.Bind<IUserSettingsManager>().To<UserSettingsManager>(); kernel.Bind<IUserSettingsManager>().To<UserSettingsManager>();
kernel.Bind<ISmsDistributionService>().To<TwillioSmsDistributionService>();
kernel.Bind<ISmsAuthenticationService>().To<SmsAuthenticationService>();
} }
} }
} }

View file

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

View file

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

View file

@ -4,7 +4,7 @@ using WebsitePanel.WebDavPortal.CustomAttributes;
namespace WebsitePanel.WebDavPortal.Models.Common.EditorTemplates namespace WebsitePanel.WebDavPortal.Models.Common.EditorTemplates
{ {
public class PasswordEditor public class PasswordEditor : BaseModel
{ {
[Display(ResourceType = typeof(Resources.UI), Name = "NewPassword")] [Display(ResourceType = typeof(Resources.UI), Name = "NewPassword")]

View file

@ -60,6 +60,15 @@ namespace WebsitePanel.WebDavPortal.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Account was not found.
/// </summary>
public static string AccountNotFound {
get {
return ResourceManager.GetString("AccountNotFound", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Email is invalid. /// Looks up a localized string similar to Email is invalid.
/// </summary> /// </summary>
@ -69,6 +78,24 @@ namespace WebsitePanel.WebDavPortal.Resources {
} }
} }
/// <summary>
/// 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..
/// </summary>
public static string IncorrectPasswordResetUrl {
get {
return ResourceManager.GetString("IncorrectPasswordResetUrl", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Incorrect text message (SMS) response..
/// </summary>
public static string IncorrectSmsResponse {
get {
return ResourceManager.GetString("IncorrectSmsResponse", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Old password is not correct. /// Looks up a localized string similar to Old password is not correct.
/// </summary> /// </summary>
@ -114,6 +141,33 @@ namespace WebsitePanel.WebDavPortal.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to A message was sent to your email address. Please check your email for further instructions..
/// </summary>
public static string PasswordResetEmailSent {
get {
return ResourceManager.GetString("PasswordResetEmailSent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Text message (SMS) was sent to the phone number associated to your account. If you didn&apos;t recieve text message (SMS) click {0} for new text message (SMS)..
/// </summary>
public static string PasswordResetSmsHintFormat {
get {
return ResourceManager.GetString("PasswordResetSmsHintFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Webdav portal user request..
/// </summary>
public static string PasswordResetUserReason {
get {
return ResourceManager.GetString("PasswordResetUserReason", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Password should contain at least {0} non-alphanumeric symbols. /// Looks up a localized string similar to Password should contain at least {0} non-alphanumeric symbols.
/// </summary> /// </summary>

View file

@ -117,9 +117,18 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="AccountNotFound" xml:space="preserve">
<value>Account was not found</value>
</data>
<data name="EmailInvalid" xml:space="preserve"> <data name="EmailInvalid" xml:space="preserve">
<value>Email is invalid</value> <value>Email is invalid</value>
</data> </data>
<data name="IncorrectPasswordResetUrl" xml:space="preserve">
<value>Your password could not be reset. The url you followed is either incorrect, has been used or has expired.</value>
</data>
<data name="IncorrectSmsResponse" xml:space="preserve">
<value>Incorrect text message (SMS) response.</value>
</data>
<data name="OldPasswordIsNotCorrect" xml:space="preserve"> <data name="OldPasswordIsNotCorrect" xml:space="preserve">
<value>Old password is not correct</value> <value>Old password is not correct</value>
</data> </data>
@ -135,6 +144,15 @@
<data name="PasswordNumbersCountFormat" xml:space="preserve"> <data name="PasswordNumbersCountFormat" xml:space="preserve">
<value>Password should contain at least {0} numbers</value> <value>Password should contain at least {0} numbers</value>
</data> </data>
<data name="PasswordResetEmailSent" xml:space="preserve">
<value>A message was sent to your email address. Please check your email for further instructions.</value>
</data>
<data name="PasswordResetSmsHintFormat" xml:space="preserve">
<value>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).</value>
</data>
<data name="PasswordResetUserReason" xml:space="preserve">
<value>Webdav portal user request.</value>
</data>
<data name="PasswordSymbolsCountFormat" xml:space="preserve"> <data name="PasswordSymbolsCountFormat" xml:space="preserve">
<value>Password should contain at least {0} non-alphanumeric symbols</value> <value>Password should contain at least {0} non-alphanumeric symbols</value>
</data> </data>

View file

@ -357,6 +357,15 @@ namespace WebsitePanel.WebDavPortal.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Forgot your password?.
/// </summary>
public static string ForgotYourPassword {
get {
return ResourceManager.GetString("ForgotYourPassword", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to General Information. /// Looks up a localized string similar to General Information.
/// </summary> /// </summary>
@ -528,6 +537,15 @@ namespace WebsitePanel.WebDavPortal.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Next.
/// </summary>
public static string Next {
get {
return ResourceManager.GetString("Next", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to No files are selected.. /// Looks up a localized string similar to No files are selected..
/// </summary> /// </summary>
@ -600,6 +618,15 @@ namespace WebsitePanel.WebDavPortal.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Password reset.
/// </summary>
public static string PasswordReset {
get {
return ResourceManager.GetString("PasswordReset", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to PB. /// Looks up a localized string similar to PB.
/// </summary> /// </summary>
@ -717,6 +744,15 @@ namespace WebsitePanel.WebDavPortal.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Send email.
/// </summary>
public static string SendEmail {
get {
return ResourceManager.GetString("SendEmail", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Size. /// Looks up a localized string similar to Size.
/// </summary> /// </summary>
@ -726,6 +762,15 @@ namespace WebsitePanel.WebDavPortal.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Sms.
/// </summary>
public static string Sms {
get {
return ResourceManager.GetString("Sms", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to State/Province. /// Looks up a localized string similar to State/Province.
/// </summary> /// </summary>

View file

@ -216,6 +216,9 @@
<data name="FirstName" xml:space="preserve"> <data name="FirstName" xml:space="preserve">
<value>First Name</value> <value>First Name</value>
</data> </data>
<data name="ForgotYourPassword" xml:space="preserve">
<value>Forgot your password?</value>
</data>
<data name="GeneralInformation" xml:space="preserve"> <data name="GeneralInformation" xml:space="preserve">
<value>General Information</value> <value>General Information</value>
</data> </data>
@ -273,6 +276,9 @@
<data name="NewPasswordConfirmation" xml:space="preserve"> <data name="NewPasswordConfirmation" xml:space="preserve">
<value>Confirm password</value> <value>Confirm password</value>
</data> </data>
<data name="Next" xml:space="preserve">
<value>Next</value>
</data>
<data name="NoFilesAreSelected" xml:space="preserve"> <data name="NoFilesAreSelected" xml:space="preserve">
<value>No files are selected.</value> <value>No files are selected.</value>
</data> </data>
@ -297,6 +303,9 @@
<data name="PasswordExpirationFormat" xml:space="preserve"> <data name="PasswordExpirationFormat" xml:space="preserve">
<value>Will expire on {0}. If you want to change password then please click {1}.</value> <value>Will expire on {0}. If you want to change password then please click {1}.</value>
</data> </data>
<data name="PasswordReset" xml:space="preserve">
<value>Password reset</value>
</data>
<data name="PetabyteShort" xml:space="preserve"> <data name="PetabyteShort" xml:space="preserve">
<value>PB</value> <value>PB</value>
</data> </data>
@ -336,9 +345,15 @@
<data name="SelectFilesToUpload" xml:space="preserve"> <data name="SelectFilesToUpload" xml:space="preserve">
<value>Select files to upload</value> <value>Select files to upload</value>
</data> </data>
<data name="SendEmail" xml:space="preserve">
<value>Send email</value>
</data>
<data name="Size" xml:space="preserve"> <data name="Size" xml:space="preserve">
<value>Size</value> <value>Size</value>
</data> </data>
<data name="Sms" xml:space="preserve">
<value>Sms</value>
</data>
<data name="State" xml:space="preserve"> <data name="State" xml:space="preserve">
<value>State/Province</value> <value>State/Province</value>
</data> </data>

View file

@ -1,8 +1,9 @@
/// <reference path="jquery-ui-1.9.0.js" /> /// <autosync enabled="true" />
/// <autosync enabled="true" />
/// <reference path="bootstrap.js" /> /// <reference path="bootstrap.js" />
/// <reference path="jquery-2.1.1.js" /> /// <reference path="jquery-2.1.1.js" />
/// <reference path="jquery-ui-1.9.0.js" />
/// <reference path="jquery.cookie.js" /> /// <reference path="jquery.cookie.js" />
/// <reference path="jquery.ui.widget.js" />
/// <reference path="jquery.validate.js" /> /// <reference path="jquery.validate.js" />
/// <reference path="jquery.validate.unobtrusive.js" /> /// <reference path="jquery.validate.unobtrusive.js" />
/// <reference path="modernizr-2.8.3.js" /> /// <reference path="modernizr-2.8.3.js" />
@ -10,5 +11,36 @@
/// <reference path="respond.js" /> /// <reference path="respond.js" />
/// <reference path="respond.matchmedia.addlistener.js" /> /// <reference path="respond.matchmedia.addlistener.js" />
/// <reference path="appscripts/authentication.js" /> /// <reference path="appscripts/authentication.js" />
/// <reference path="appscripts/dialogs.js" />
/// <reference path="appscripts/filebrowsing.js" />
/// <reference path="appscripts/messages.js" />
/// <reference path="appscripts/recalculateresourseheight.js" /> /// <reference path="appscripts/recalculateresourseheight.js" />
/// <reference path="appscripts/uploadingdata2.js" /> /// <reference path="appscripts/uploadingdata2.js" />
/// <reference path="appscripts/wsp-webdav.js" />
/// <reference path="appscripts/wsp.js" />
/// <reference path="datatables-1.10.4/datatables.autofill.js" />
/// <reference path="datatables-1.10.4/datatables.bootstrap.js" />
/// <reference path="datatables-1.10.4/datatables.colreorder.js" />
/// <reference path="datatables-1.10.4/datatables.colvis.js" />
/// <reference path="datatables-1.10.4/datatables.fixedcolumns.js" />
/// <reference path="datatables-1.10.4/datatables.fixedheader.js" />
/// <reference path="datatables-1.10.4/datatables.foundation.js" />
/// <reference path="datatables-1.10.4/datatables.jqueryui.js" />
/// <reference path="datatables-1.10.4/datatables.keytable.js" />
/// <reference path="datatables-1.10.4/datatables.responsive.js" />
/// <reference path="datatables-1.10.4/datatables.scroller.js" />
/// <reference path="datatables-1.10.4/datatables.tabletools.js" />
/// <reference path="datatables-1.10.4/jquery.datatables.js" />
/// <reference path="jquery.fileupload/jquery.fileupload-angular.js" />
/// <reference path="jquery.fileupload/jquery.fileupload-audio.js" />
/// <reference path="jquery.fileupload/jquery.fileupload-image.js" />
/// <reference path="jquery.fileupload/jquery.fileupload-jquery-ui.js" />
/// <reference path="jquery.fileupload/jquery.fileupload-process.js" />
/// <reference path="jquery.fileupload/jquery.fileupload-ui.js" />
/// <reference path="jquery.fileupload/jquery.fileupload-validate.js" />
/// <reference path="jquery.fileupload/jquery.fileupload-video.js" />
/// <reference path="jquery.fileupload/jquery.fileupload.js" />
/// <reference path="jquery.fileupload/jquery.iframe-transport.js" />
/// <reference path="jquery.fileupload/tmpl.min.js" />
/// <reference path="jquery.fileupload/cors/jquery.postmessage-transport.js" />
/// <reference path="jquery.fileupload/cors/jquery.xdr-transport.js" />

View file

@ -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"; ViewBag.Title = "Login";
} }
@ -30,6 +32,7 @@
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Sign in</button> <button type="submit" class="btn btn-default">Sign in</button>
<a href="@Url.RouteUrl(AccountRouteNames.PasswordResetEmail)" class="forgot-your-password-link">@UI.ForgotYourPassword</a>
</div> </div>
</div> </div>
</form> </form>

View file

@ -0,0 +1,31 @@
@using WebsitePanel.WebDavPortal.Resources
@using WebsitePanel.WebDavPortal.UI.Routes
@model WebsitePanel.WebDavPortal.Models.Account.PasswordResetEmailModel
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="container row">
@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" }))
{
<div class="form-group">
<h3>@UI.PasswordReset</h3>
</div>
<div class="form-group">
<label for="@Html.IdFor(x => x.Email)" class="col-sm-2 control-label">@UI.Email</label>
<div class="col-sm-10">
@Html.TextBoxFor(x => x.Email, new { @class = "form-control", placeholder = UI.Email })
@Html.ValidationMessageFor(x => x.Email)
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">@UI.SendEmail</button>
</div>
</div>
}
</div>

View file

@ -0,0 +1,14 @@
@using WebsitePanel.WebDavPortal.Resources
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="container row">
<div class="form-group">
<h3>@UI.PasswordReset</h3>
<h4>@Messages.PasswordResetEmailSent</h4>
</div>
</div>

View file

@ -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
<div class="container row">
@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" }))
{
<div class="form-group">
<h3>@UI.PasswordReset</h3>
</div>
@Html.EditorFor(x=>x)
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">@UI.ChangePassword</button>
</div>
</div>
}
</div>

View file

@ -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)
{
<div class="container row">
@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)
<div class="form-group">
<h3>@UI.PasswordReset</h3>
</div>
<div class="form-group">
<span>@Html.Raw(string.Format(Messages.PasswordResetSmsHintFormat, Html.RouteLink(UI.Here.ToLowerInvariant(), AccountRouteNames.PasswordResetSendSms)))</span>
</div>
<div class="form-group">
<label for="@Html.IdFor(x => x.Sms)" class="col-sm-2 control-label">@UI.Sms</label>
<div class="col-sm-10">
@Html.TextBoxFor(x => x.Sms, new {@class = "form-control", placeholder = UI.Sms})
@Html.ValidationMessageFor(x => x.Sms)
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">@UI.Next</button>
</div>
</div>
}
</div>
}

View file

@ -43,7 +43,8 @@
</div> </div>
</div> </div>
<div class="container body-content"> <div class="container body-content">
<div id="message-area" class="container prevent-deselect"> </div> <div id="message-area" class="container row prevent-deselect"> </div>
@RenderBody() @RenderBody()
</div> </div>

View file

@ -52,7 +52,8 @@
<add key="ResourseRenderCountSessionKey" value="ResourseRenderCount" /> <add key="ResourseRenderCountSessionKey" value="ResourseRenderCount" />
<add key="ItemIdSessionKey" value="ItemId" /> <add key="ItemIdSessionKey" value="ItemId" />
<add key="UserGroupsKey" value="UserGroups" /> <add key="UserGroupsKey" value="UserGroups" />
<add key="OwaEditFoldersSession" value="OwaEditFolders"/> <add key="OwaEditFoldersSession" value="OwaEditFolders" />
<add key="PassswordResetSmsKey" value="PassswordResetSms" />
</sessionKeys> </sessionKeys>
<fileIcons defaultPath="~/Content/Images/other-icon.png" folderPath="~/Content/Images/folder_100x100.png"> <fileIcons defaultPath="~/Content/Images/other-icon.png" folderPath="~/Content/Images/folder_100x100.png">
<add extension=".txt" path="~/Content/Images/txt-icon.png" /> <add extension=".txt" path="~/Content/Images/txt-icon.png" />
@ -86,12 +87,12 @@
<add browser="Safari" version="4" /> <add browser="Safari" version="4" />
</owaSupportedBrowsers> </owaSupportedBrowsers>
<officeOnline isEnabled="True" url="https://vir-owa.virtuworks.net" cobaltFileTtl="1" cobaltNewFilePath="~/Content/OwaFiles/New"> <officeOnline isEnabled="True" url="https://vir-owa.virtuworks.net" cobaltFileTtl="1" cobaltNewFilePath="~/Content/OwaFiles/New">
<add extension=".doc" OwaView="wv/wordviewerframe.aspx?" OwaEditor="wv/wordviewerframe.aspx?" OwaMobileView="wv/mWord.aspx?wdMobileHost=3&amp;" OwaNewFileView="we/wordeditorframe.aspx?new=1&amp;"/> <add extension=".doc" OwaView="wv/wordviewerframe.aspx?" OwaEditor="wv/wordviewerframe.aspx?" OwaMobileView="wv/mWord.aspx?wdMobileHost=3&amp;" OwaNewFileView="we/wordeditorframe.aspx?new=1&amp;" />
<add extension=".docx" OwaView="wv/wordviewerframe.aspx?" OwaEditor="we/wordeditorframe.aspx?" OwaMobileView="wv/mWord.aspx?wdMobileHost=3&amp;" OwaNewFileView="we/wordeditorframe.aspx?new=1&amp;"/> <add extension=".docx" OwaView="wv/wordviewerframe.aspx?" OwaEditor="we/wordeditorframe.aspx?" OwaMobileView="wv/mWord.aspx?wdMobileHost=3&amp;" OwaNewFileView="we/wordeditorframe.aspx?new=1&amp;" />
<add extension=".xls" OwaView="x/_layouts/xlviewerinternal.aspx?" OwaEditor="x/_layouts/xlviewerinternal.aspx?edit=1&amp;" OwaMobileView="x/_layouts/mobile/mXL.aspx?wdMobileHost=3&amp;" OwaNewFileView="x/_layouts/xlviewerinternal.aspx?new=1&amp;"/> <add extension=".xls" OwaView="x/_layouts/xlviewerinternal.aspx?" OwaEditor="x/_layouts/xlviewerinternal.aspx?edit=1&amp;" OwaMobileView="x/_layouts/mobile/mXL.aspx?wdMobileHost=3&amp;" OwaNewFileView="x/_layouts/xlviewerinternal.aspx?new=1&amp;" />
<add extension=".xlsx" OwaView="x/_layouts/xlviewerinternal.aspx?" OwaEditor="x/_layouts/xlviewerinternal.aspx?edit=1&amp;" OwaMobileView="x/_layouts/mobile/mXL.aspx?wdMobileHost=3&amp;" OwaNewFileView="x/_layouts/xlviewerinternal.aspx?edit=1&amp;"/> <add extension=".xlsx" OwaView="x/_layouts/xlviewerinternal.aspx?" OwaEditor="x/_layouts/xlviewerinternal.aspx?edit=1&amp;" OwaMobileView="x/_layouts/mobile/mXL.aspx?wdMobileHost=3&amp;" OwaNewFileView="x/_layouts/xlviewerinternal.aspx?edit=1&amp;" />
<add extension=".ppt" OwaView="p/PowerPointFrame.aspx?" OwaEditor="p/PowerPointFrame.aspx?" OwaMobileView="p/mPPT.aspx?wdMobileHost=3&amp;" OwaNewFileView="p/PowerPointFrame.aspx?PowerPointView=EditView&amp;New=1&amp;"/> <add extension=".ppt" OwaView="p/PowerPointFrame.aspx?" OwaEditor="p/PowerPointFrame.aspx?" OwaMobileView="p/mPPT.aspx?wdMobileHost=3&amp;" OwaNewFileView="p/PowerPointFrame.aspx?PowerPointView=EditView&amp;New=1&amp;" />
<add extension=".pptx" OwaView="p/PowerPointFrame.aspx?" OwaEditor="p/PowerPointFrame.aspx?PowerPointView=EditView&amp;" OwaMobileView="p/mPPT.aspx?wdMobileHost=3&amp;" OwaNewFileView="p/PowerPointFrame.aspx?PowerPointView=EditView&amp;New=1&amp;"/> <add extension=".pptx" OwaView="p/PowerPointFrame.aspx?" OwaEditor="p/PowerPointFrame.aspx?PowerPointView=EditView&amp;" OwaMobileView="p/mPPT.aspx?wdMobileHost=3&amp;" OwaNewFileView="p/PowerPointFrame.aspx?PowerPointView=EditView&amp;New=1&amp;" />
</officeOnline> </officeOnline>
<typeOpener> <typeOpener>
<add extension=".jpg" mimeType="image/jpeg" isTargetBlank="true" /> <add extension=".jpg" mimeType="image/jpeg" isTargetBlank="true" />
@ -100,6 +101,7 @@
<add extension=".txt" mimeType="text/plain" isTargetBlank="true" /> <add extension=".txt" mimeType="text/plain" isTargetBlank="true" />
<add extension=".pdf" mimeType="application/pdf" isTargetBlank="true" /> <add extension=".pdf" mimeType="application/pdf" isTargetBlank="true" />
</typeOpener> </typeOpener>
<twilio accountSid="s" authorizationToken="s" phoneFrom="s"/>
</webDavExplorerConfigurationSettings> </webDavExplorerConfigurationSettings>
<!-- <!--
For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367. For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

View file

@ -195,6 +195,8 @@
<Compile Include="ModelBinders\DataTables\JqueryDataTableModelBinder.cs" /> <Compile Include="ModelBinders\DataTables\JqueryDataTableModelBinder.cs" />
<Compile Include="Models\AccountModel.cs" /> <Compile Include="Models\AccountModel.cs" />
<Compile Include="Models\Account\PasswordChangeModel.cs" /> <Compile Include="Models\Account\PasswordChangeModel.cs" />
<Compile Include="Models\Account\PasswordResetEmailModel.cs" />
<Compile Include="Models\Account\PasswordResetSmsModel.cs" />
<Compile Include="Models\Account\UserProfile.cs" /> <Compile Include="Models\Account\UserProfile.cs" />
<Compile Include="Models\Common\BaseModel.cs" /> <Compile Include="Models\Common\BaseModel.cs" />
<Compile Include="Models\Common\DataTable\JqueryDataTableBaseEntity.cs" /> <Compile Include="Models\Common\DataTable\JqueryDataTableBaseEntity.cs" />
@ -481,6 +483,10 @@
<Content Include="Views\Shared\EditorTemplates\CountrySelector.cshtml" /> <Content Include="Views\Shared\EditorTemplates\CountrySelector.cshtml" />
<Content Include="Views\Account\PasswordChange.cshtml" /> <Content Include="Views\Account\PasswordChange.cshtml" />
<Content Include="Views\Shared\EditorTemplates\PasswordEditor.cshtml" /> <Content Include="Views\Shared\EditorTemplates\PasswordEditor.cshtml" />
<Content Include="Views\Account\PasswordResetEmail.cshtml" />
<Content Include="Views\Account\PasswordResetEmailSent.cshtml" />
<Content Include="Views\Account\PasswordResetSms.cshtml" />
<Content Include="Views\Account\PasswordResetFinalStep.cshtml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Models\FileSystem\Enums\" /> <Folder Include="Models\FileSystem\Enums\" />

View file

@ -130,7 +130,7 @@
<value>Password settings</value> <value>Password settings</value>
</data> </data>
<data name="lblAccountLockoutDuration.Text" xml:space="preserve"> <data name="lblAccountLockoutDuration.Text" xml:space="preserve">
<value>Account lockout duration:</value> <value>Account lockout duration (minutes):</value>
</data> </data>
<data name="lblEnforcePasswordHistory.Text" xml:space="preserve"> <data name="lblEnforcePasswordHistory.Text" xml:space="preserve">
<value>Enforce password history:</value> <value>Enforce password history:</value>

View file

@ -14,7 +14,7 @@
<div class="FormBody"> <div class="FormBody">
<table> <table>
<tr class="OrgStatsRow"> <tr class="OrgStatsRow">
<td align="right"> <td>
<asp:Label runat="server" ID="lblOrganizationName" meta:resourcekey="lblOrganizationName" /> <asp:Label runat="server" ID="lblOrganizationName" meta:resourcekey="lblOrganizationName" />
</td> </td>
<td> <td>

View file

@ -72,7 +72,7 @@
</tr> </tr>
<tr id="RowAccountLockoutDuration" runat="server"> <tr id="RowAccountLockoutDuration" runat="server">
<td class="Normal" style="width:150px;"><asp:Label ID="lblAccountLockoutDuration" runat="server" <td class="Normal" style="width:150px;"><asp:Label ID="lblAccountLockoutDuration" runat="server"
meta:resourcekey="lblAccountLockoutDuration" Text="Account Lockout Duration:"></asp:Label></td> meta:resourcekey="lblAccountLockoutDuration" Text="Account Lockout Duration (minutes):"></asp:Label></td>
<td class="Normal"> <td class="Normal">
<asp:TextBox ID="txtAccountLockoutDuration" runat="server" CssClass="NormalTextBox" Width="40px"></asp:TextBox> <asp:TextBox ID="txtAccountLockoutDuration" runat="server" CssClass="NormalTextBox" Width="40px"></asp:TextBox>
<asp:RequiredFieldValidator ID="valRequireAccountLockoutDuration" runat="server" ControlToValidate="txtAccountLockoutDuration" meta:resourcekey="valRequireAccountLockoutDuration" <asp:RequiredFieldValidator ID="valRequireAccountLockoutDuration" runat="server" ControlToValidate="txtAccountLockoutDuration" meta:resourcekey="valRequireAccountLockoutDuration"

View file

@ -130,7 +130,7 @@
<value>Enable Password Complexity</value> <value>Enable Password Complexity</value>
</data> </data>
<data name="lblAccountLockoutDuration.Text" xml:space="preserve"> <data name="lblAccountLockoutDuration.Text" xml:space="preserve">
<value>Account lockout duration:</value> <value>Account lockout duration (minutes):</value>
</data> </data>
<data name="lblEnforcePasswordHistory.Text" xml:space="preserve"> <data name="lblEnforcePasswordHistory.Text" xml:space="preserve">
<value>Enforce password history:</value> <value>Enforce password history:</value>

View file

@ -45,7 +45,7 @@
</tr> </tr>
<tr id="RowAccountLockoutDuration" runat="server"> <tr id="RowAccountLockoutDuration" runat="server">
<td class="Normal" style="width:150px;"><asp:Label ID="lblAccountLockoutDuration" runat="server" <td class="Normal" style="width:150px;"><asp:Label ID="lblAccountLockoutDuration" runat="server"
meta:resourcekey="lblAccountLockoutDuration" Text="Account Lockout Duration:"></asp:Label></td> meta:resourcekey="lblAccountLockoutDuration" Text="Account Lockout Duration (minutes):"></asp:Label></td>
<td class="Normal"> <td class="Normal">
<asp:TextBox ID="txtAccountLockoutDuration" runat="server" CssClass="NormalTextBox" Width="40px"></asp:TextBox> <asp:TextBox ID="txtAccountLockoutDuration" runat="server" CssClass="NormalTextBox" Width="40px"></asp:TextBox>
<asp:RequiredFieldValidator ID="valRequireAccountLockoutDuration" runat="server" ControlToValidate="txtAccountLockoutDuration" meta:resourcekey="valRequireAccountLockoutDuration" <asp:RequiredFieldValidator ID="valRequireAccountLockoutDuration" runat="server" ControlToValidate="txtAccountLockoutDuration" meta:resourcekey="valRequireAccountLockoutDuration"

View file

@ -4655,7 +4655,9 @@
<Content Include="ExchangeServer\App_LocalResources\OrganizationUserResetPassword.ascx.resx" /> <Content Include="ExchangeServer\App_LocalResources\OrganizationUserResetPassword.ascx.resx" />
<Content Include="App_LocalResources\SettingsUserPasswordExpirationLetter.ascx.resx" /> <Content Include="App_LocalResources\SettingsUserPasswordExpirationLetter.ascx.resx" />
<Content Include="ExchangeServer\UserControls\App_LocalResources\OrganizationSettingsTabs.ascx.resx" /> <Content Include="ExchangeServer\UserControls\App_LocalResources\OrganizationSettingsTabs.ascx.resx" />
<Content Include="ExchangeServer\App_LocalResources\OrganizationSettingsPasswordSettings.ascx.resx" /> <Content Include="ExchangeServer\App_LocalResources\OrganizationSettingsPasswordSettings.ascx.resx">
<SubType>Designer</SubType>
</Content>
<Content Include="ExchangeServer\App_LocalResources\OrganizationSettingsGeneralSettings.ascx.resx" /> <Content Include="ExchangeServer\App_LocalResources\OrganizationSettingsGeneralSettings.ascx.resx" />
<Content Include="App_LocalResources\SettingsUserPasswordResetLetter.ascx.resx" /> <Content Include="App_LocalResources\SettingsUserPasswordResetLetter.ascx.resx" />
<EmbeddedResource Include="RDS\App_LocalResources\RDSEditCollectionSettings.ascx.resx" /> <EmbeddedResource Include="RDS\App_LocalResources\RDSEditCollectionSettings.ascx.resx" />