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,
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

View file

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

View file

@ -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";
}
/// <remarks/>
public event DeletePasswordresetAccessTokenCompletedEventHandler DeletePasswordresetAccessTokenCompleted;
/// <remarks/>
public event SetAccessTokenResponseCompletedEventHandler SetAccessTokenResponseCompleted;
/// <remarks/>
public event GetPasswordresetAccessTokenCompletedEventHandler GetPasswordresetAccessTokenCompleted;
/// <remarks/>
public event UpdateOrganizationGeneralSettingsCompletedEventHandler UpdateOrganizationGeneralSettingsCompleted;
@ -321,6 +337,128 @@ namespace WebsitePanel.EnterpriseServer.HostedSolution {
/// <remarks/>
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/>
[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 {
}
}
/// <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/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
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)
{
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,

View file

@ -1625,6 +1625,11 @@ namespace WebsitePanel.EnterpriseServer
return ObjectUtils.FillObjectFromDataReader<AccessToken>(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");

View file

@ -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)
{

View file

@ -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,7 +512,15 @@ namespace WebsitePanel.Providers.HostedSolution
return result;
}
var maxPasswordAgeSpan = GetMaxPasswordAge();
Runspace runspace = null;
try
{
runspace = OpenRunspace();
var psoName = FormOrganizationPSOName(organizationId);
var maxPasswordAgeSpan = GetMaxPasswordAge(runspace, psoName);
var searchRoot = new DirectoryEntry(GetOrganizationPath(organizationId));
@ -512,7 +537,7 @@ namespace WebsitePanel.Providers.HostedSolution
foreach (SearchResult searchResult in searchResults)
{
var pwdLastSetTicks = (long)searchResult.Properties["pwdLastSet"][0];
var pwdLastSetTicks = (long) searchResult.Properties["pwdLastSet"][0];
var pwdLastSetDate = DateTime.FromFileTimeUtc(pwdLastSetTicks);
@ -523,17 +548,39 @@ namespace WebsitePanel.Providers.HostedSolution
var user = new OrganizationUser();
user.PasswordExpirationDateTime = expirationDate;
user.SamAccountName = (string)searchResult.Properties["sAMAccountName"][0];
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);
try
{
var cmd = new Command("Get-ADFineGrainedPasswordPolicy");
cmd.Parameters.Add("Identity", psoName);
if (string.IsNullOrEmpty(gpoId))
var result = ExecuteShellCommand(runspace, cmd);
}
catch (Exception e)
{
gpoId = CreateAndLinkPolicy(runspace, gpoName, organizationId);
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<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
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)
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<PSObject> 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,14 +953,22 @@ 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;
try
{
runspace = OpenRunspace();
var maxPasswordAgeSpan = GetMaxPasswordAge(runspace, psoName);
var pwdLastSetTicks = ConvertADSLargeIntegerToInt64(entry.Properties[ADAttributes.PwdLastSet].Value);
@ -886,6 +976,12 @@ namespace WebsitePanel.Providers.HostedSolution
return pwdLastSetDate.AddDays(maxPasswordAgeSpan.Days);
}
finally
{
CloseRunspace(runspace);
}
}
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
{

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
{
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; }
ElementsRendering ElementsRendering { get; }
WebsitePanelConstantUserParameters WebsitePanelConstantUserParameters { get; }
TwilioParameters TwilioParameters { get; }
SessionKeysCollection SessionKeys { get; }
FileIconsDictionary FileIcons { get; }
HttpErrorsCollection HttpErrors { get; }

View file

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

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 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
{

View file

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

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>
<HintPath>..\..\Bin\Microsoft.Web.Services3.dll</HintPath>
</Reference>
<Reference Include="RestSharp">
<HintPath>..\packages\RestSharp.105.0.1\lib\net4\RestSharp.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
@ -87,6 +90,10 @@
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<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">
<HintPath>..\..\Bin\WebsitePanel.EnterpriseServer.Base.dll</HintPath>
</Reference>
@ -108,6 +115,7 @@
<Compile Include="Config\Entities\OwaSupportedBrowsersCollection.cs" />
<Compile Include="Config\Entities\SessionKeysCollection.cs" />
<Compile Include="Config\Entities\OpenerCollection.cs" />
<Compile Include="Config\Entities\TwilioParameters.cs" />
<Compile Include="Config\Entities\WebsitePanelConstantUserParameters.cs" />
<Compile Include="Config\IWebDavAppConfig.cs" />
<Compile Include="Config\WebConfigSections\ApplicationNameElement.cs" />
@ -126,6 +134,7 @@
<Compile Include="Config\WebConfigSections\SessionKeysElement.cs" />
<Compile Include="Config\WebConfigSections\SessionKeysElementCollection.cs" />
<Compile Include="Config\WebConfigSections\OpenerElement.cs" />
<Compile Include="Config\WebConfigSections\TwilioElement.cs" />
<Compile Include="Config\WebConfigSections\UserDomainElement.cs" />
<Compile Include="Config\WebConfigSections\WebDavExplorerConfigurationSettingsSection.cs" />
<Compile Include="Config\WebConfigSections\WebdavRootElement.cs" />
@ -149,6 +158,9 @@
<Compile Include="IHierarchyItem.cs" />
<Compile Include="IItemContent.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\ITtlStorage.cs" />
<Compile Include="Managers\Users\UserSettingsManager.cs" />
@ -188,6 +200,7 @@
<Compile Include="Security\Authentication\FormsAuthenticationService.cs" />
<Compile Include="Security\Authentication\Principals\WspPrincipal.cs" />
<Compile Include="Owa\WopiServer.cs" />
<Compile Include="Services\TwillioSmsDistributionService.cs" />
<Compile Include="Storages\CacheTtlStorage.cs" />
<Compile Include="WebDavSession.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.WebPages" version="3.2.2" 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>

View file

@ -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",

View file

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

View file

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

View file

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

View file

@ -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<string>();
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.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<ICobaltManager>().To<CobaltManager>();
kernel.Bind<ITtlStorage>().To<CacheTtlStorage>();
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
{
public class PasswordEditor
public class PasswordEditor : BaseModel
{
[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>
/// Looks up a localized string similar to Email is invalid.
/// </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>
/// Looks up a localized string similar to Old password is not correct.
/// </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>
/// Looks up a localized string similar to Password should contain at least {0} non-alphanumeric symbols.
/// </summary>

View file

@ -117,9 +117,18 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AccountNotFound" xml:space="preserve">
<value>Account was not found</value>
</data>
<data name="EmailInvalid" xml:space="preserve">
<value>Email is invalid</value>
</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">
<value>Old password is not correct</value>
</data>
@ -135,6 +144,15 @@
<data name="PasswordNumbersCountFormat" xml:space="preserve">
<value>Password should contain at least {0} numbers</value>
</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">
<value>Password should contain at least {0} non-alphanumeric symbols</value>
</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>
/// Looks up a localized string similar to General Information.
/// </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>
/// Looks up a localized string similar to No files are selected..
/// </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>
/// Looks up a localized string similar to PB.
/// </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>
/// Looks up a localized string similar to Size.
/// </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>
/// Looks up a localized string similar to State/Province.
/// </summary>

View file

@ -216,6 +216,9 @@
<data name="FirstName" xml:space="preserve">
<value>First Name</value>
</data>
<data name="ForgotYourPassword" xml:space="preserve">
<value>Forgot your password?</value>
</data>
<data name="GeneralInformation" xml:space="preserve">
<value>General Information</value>
</data>
@ -273,6 +276,9 @@
<data name="NewPasswordConfirmation" xml:space="preserve">
<value>Confirm password</value>
</data>
<data name="Next" xml:space="preserve">
<value>Next</value>
</data>
<data name="NoFilesAreSelected" xml:space="preserve">
<value>No files are selected.</value>
</data>
@ -297,6 +303,9 @@
<data name="PasswordExpirationFormat" xml:space="preserve">
<value>Will expire on {0}. If you want to change password then please click {1}.</value>
</data>
<data name="PasswordReset" xml:space="preserve">
<value>Password reset</value>
</data>
<data name="PetabyteShort" xml:space="preserve">
<value>PB</value>
</data>
@ -336,9 +345,15 @@
<data name="SelectFilesToUpload" xml:space="preserve">
<value>Select files to upload</value>
</data>
<data name="SendEmail" xml:space="preserve">
<value>Send email</value>
</data>
<data name="Size" xml:space="preserve">
<value>Size</value>
</data>
<data name="Sms" xml:space="preserve">
<value>Sms</value>
</data>
<data name="State" xml:space="preserve">
<value>State/Province</value>
</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="jquery-2.1.1.js" />
/// <reference path="jquery-ui-1.9.0.js" />
/// <reference path="jquery.cookie.js" />
/// <reference path="jquery.ui.widget.js" />
/// <reference path="jquery.validate.js" />
/// <reference path="jquery.validate.unobtrusive.js" />
/// <reference path="modernizr-2.8.3.js" />
@ -10,5 +11,36 @@
/// <reference path="respond.js" />
/// <reference path="respond.matchmedia.addlistener.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/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";
}
@ -30,6 +32,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<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>
</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 class="container body-content">
<div id="message-area" class="container prevent-deselect"> </div>
<div id="message-area" class="container row prevent-deselect"> </div>
@RenderBody()
</div>

View file

@ -52,7 +52,8 @@
<add key="ResourseRenderCountSessionKey" value="ResourseRenderCount" />
<add key="ItemIdSessionKey" value="ItemId" />
<add key="UserGroupsKey" value="UserGroups" />
<add key="OwaEditFoldersSession" value="OwaEditFolders"/>
<add key="OwaEditFoldersSession" value="OwaEditFolders" />
<add key="PassswordResetSmsKey" value="PassswordResetSms" />
</sessionKeys>
<fileIcons defaultPath="~/Content/Images/other-icon.png" folderPath="~/Content/Images/folder_100x100.png">
<add extension=".txt" path="~/Content/Images/txt-icon.png" />
@ -86,12 +87,12 @@
<add browser="Safari" version="4" />
</owaSupportedBrowsers>
<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=".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=".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=".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=".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=".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=".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;" />
</officeOnline>
<typeOpener>
<add extension=".jpg" mimeType="image/jpeg" isTargetBlank="true" />
@ -100,6 +101,7 @@
<add extension=".txt" mimeType="text/plain" isTargetBlank="true" />
<add extension=".pdf" mimeType="application/pdf" isTargetBlank="true" />
</typeOpener>
<twilio accountSid="s" authorizationToken="s" phoneFrom="s"/>
</webDavExplorerConfigurationSettings>
<!--
For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

View file

@ -195,6 +195,8 @@
<Compile Include="ModelBinders\DataTables\JqueryDataTableModelBinder.cs" />
<Compile Include="Models\AccountModel.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\Common\BaseModel.cs" />
<Compile Include="Models\Common\DataTable\JqueryDataTableBaseEntity.cs" />
@ -481,6 +483,10 @@
<Content Include="Views\Shared\EditorTemplates\CountrySelector.cshtml" />
<Content Include="Views\Account\PasswordChange.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>
<Folder Include="Models\FileSystem\Enums\" />

View file

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

View file

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

View file

@ -72,7 +72,7 @@
</tr>
<tr id="RowAccountLockoutDuration" 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">
<asp:TextBox ID="txtAccountLockoutDuration" runat="server" CssClass="NormalTextBox" Width="40px"></asp:TextBox>
<asp:RequiredFieldValidator ID="valRequireAccountLockoutDuration" runat="server" ControlToValidate="txtAccountLockoutDuration" meta:resourcekey="valRequireAccountLockoutDuration"

View file

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

View file

@ -45,7 +45,7 @@
</tr>
<tr id="RowAccountLockoutDuration" 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">
<asp:TextBox ID="txtAccountLockoutDuration" runat="server" CssClass="NormalTextBox" Width="40px"></asp:TextBox>
<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="App_LocalResources\SettingsUserPasswordExpirationLetter.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="App_LocalResources\SettingsUserPasswordResetLetter.ascx.resx" />
<EmbeddedResource Include="RDS\App_LocalResources\RDSEditCollectionSettings.ascx.resx" />