This commit is contained in:
Virtuworks 2015-04-29 18:42:10 -04:00
commit 3888f9e8cf
18 changed files with 251 additions and 23 deletions

View file

@ -56,6 +56,7 @@ namespace WebsitePanel.EnterpriseServer
public const string TWILIO_PHONEFROM_KEY = "TwilioPhoneFrom";
public const string WEBDAV_PASSWORD_RESET_ENABLED_KEY = "WebdavPswResetEnabled";
public const string WEBDAV_PASSWORD_RESET_LINK_LIFE_SPAN = "WebdavPswdResetLinkLifeSpan";
// key to access to wpi main & custom feed in wpi settings
public const string WPI_MAIN_FEED_KEY = "WpiMainFeedUrl";

View file

@ -1877,7 +1877,9 @@ namespace WebsitePanel.EnterpriseServer
var webdavPortalUrl = new Uri(settings["WebdavPortalUrl"]);
var token = CreateAccessToken(itemId, accountId, AccessTokenTypes.PasswrodReset);
var hours = settings.GetValueOrDefault(SystemSettings.WEBDAV_PASSWORD_RESET_LINK_LIFE_SPAN, 1);
var token = CreateAccessToken(itemId, accountId, AccessTokenTypes.PasswrodReset, hours);
tokenGuid = token.AccessTokenGuid;
@ -1892,7 +1894,7 @@ namespace WebsitePanel.EnterpriseServer
return resultUrl.ToString();
}
private static AccessToken CreateAccessToken(int itemId, int accountId, AccessTokenTypes type)
private static AccessToken CreateAccessToken(int itemId, int accountId, AccessTokenTypes type, int hours)
{
var token = new AccessToken
{
@ -1900,7 +1902,7 @@ namespace WebsitePanel.EnterpriseServer
ItemId = itemId,
AccountId = accountId,
TokenType = type,
ExpirationDate = DateTime.Now.AddHours(12)
ExpirationDate = DateTime.Now.AddHours(hours)
};
token.Id = DataProvider.AddAccessToken(token);
@ -2890,6 +2892,7 @@ namespace WebsitePanel.EnterpriseServer
OrganizationUser retUser = orgProxy.GetOrganizationUserWithExtraData(accountName, org.OrganizationId);
retUser.AccountId = accountId;
retUser.ItemId = itemId;
retUser.AccountName = account.AccountName;
retUser.PrimaryEmailAddress = account.PrimaryEmailAddress;
retUser.AccountType = account.AccountType;

View file

@ -576,6 +576,11 @@ namespace WebsitePanel.Providers.HostedSolution
if (span != null)
{
if (span.Value.Duration() == new TimeSpan().Duration())
{
return TimeSpan.MaxValue;
}
return span.Value;
}
}
@ -679,15 +684,20 @@ namespace WebsitePanel.Providers.HostedSolution
if (!FineGrainedPasswordPolicyExist(runspace, psoName))
{
CreateFineGrainedPasswordPolicy(runspace, organizationId, psoName, settings);
string groupPath = GetGroupPath(organizationId);
SetFineGrainedPasswordPolicySubject(runspace, groupPath, psoName);
}
else
{
UpdateFineGrainedPasswordPolicy(runspace, psoName, settings);
}
string groupPath = GetGroupPath(organizationId);
SetFineGrainedPasswordPolicySubject(runspace, groupPath, psoName);
if (settings.MaxPasswordAge == 0)
{
SetPasswordNeverExpiresInFineGrainedPasswordPolicy(runspace, psoName);
}
}
catch (Exception ex)
{
@ -706,6 +716,24 @@ namespace WebsitePanel.Providers.HostedSolution
return string.Format("{0}-PSO", organizationId);
}
private void SetPasswordNeverExpiresInFineGrainedPasswordPolicy(Runspace runspace, string psoName)
{
var psoObject = GetFineGrainedPasswordPolicy(runspace, psoName);
var distinguishedName = GetPSObjectProperty(psoObject, "DistinguishedName") as string;
var cmd = new Command("Set-ADObject");
cmd.Parameters.Add("Identity", distinguishedName);
var hashTable = new Hashtable();
hashTable.Add("msDS-MaximumPasswordAge", "-9223372036854775808");
cmd.Parameters.Add("Replace", hashTable);
ExecuteShellCommand(runspace, cmd);
}
private bool FineGrainedPasswordPolicyExist(Runspace runspace, string psoName)
{
try
@ -759,12 +787,12 @@ namespace WebsitePanel.Providers.HostedSolution
var cmd = new Command("Add-ADFineGrainedPasswordPolicySubject");
cmd.Parameters.Add("Identity", psoName);
cmd.Parameters.Add("Subjects", entry.Properties[ADAttributes.SAMAccountName].Value.ToString());
cmd.Parameters.Add("Subjects", entry.Properties[ADAttributes.DistinguishedName].Value.ToString());
ExecuteShellCommand(runspace, cmd);
cmd = new Command("Set-ADGroup");
cmd.Parameters.Add("Identity", entry.Properties[ADAttributes.SAMAccountName].Value.ToString());
cmd.Parameters.Add("Identity", entry.Properties[ADAttributes.DistinguishedName].Value.ToString());
cmd.Parameters.Add("GroupScope", "Global");
ExecuteShellCommand(runspace, cmd);

View file

@ -267,15 +267,21 @@ namespace WebsitePanel.WebDavPortal.Controllers
return result;
}
var tokenEntity = WspContext.Services.Organizations.GetPasswordresetAccessToken(token);
var account = WspContext.Services.Organizations.GetUserGeneralSettings(tokenEntity.ItemId,
tokenEntity.AccountId);
var model = new PasswordEditor();
var model = new PasswordResetFinalStepModel();
model.PasswordEditor.Settings = WspContext.Services.Organizations.GetOrganizationPasswordSettings(tokenEntity.ItemId);
model.Login = account.UserPrincipalName;
return View(model);
}
[HttpPost]
[AllowAnonymous]
public ActionResult PasswordResetFinalStep(Guid token, string pincode, PasswordEditor model)
public ActionResult PasswordResetFinalStep(Guid token, string pincode, PasswordResetFinalStepModel model)
{
if (!ModelState.IsValid)
{
@ -293,12 +299,10 @@ namespace WebsitePanel.WebDavPortal.Controllers
WspContext.Services.Organizations.SetUserPassword(
tokenEntity.ItemId, tokenEntity.AccountId,
model.NewPassword);
model.PasswordEditor.NewPassword);
WspContext.Services.Organizations.DeletePasswordresetAccessToken(token);
AddMessage(MessageType.Success, Resources.Messages.PasswordSuccessfullyChanged);
return RedirectToRoute(AccountRouteNames.PasswordResetSuccess);
}

View file

@ -0,0 +1,15 @@
using WebsitePanel.WebDavPortal.Models.Common.EditorTemplates;
namespace WebsitePanel.WebDavPortal.Models.Account
{
public class PasswordResetFinalStepModel
{
public PasswordResetFinalStepModel()
{
PasswordEditor = new PasswordEditor();
}
public string Login { get; set; }
public PasswordEditor PasswordEditor { get; set; }
}
}

View file

@ -654,6 +654,15 @@ namespace WebsitePanel.WebDavPortal.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Password must meet the following requirements:.
/// </summary>
public static string PasswordFollowingRequirements {
get {
return ResourceManager.GetString("PasswordFollowingRequirements", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password never expires. If you want to change password then please click {0}..
/// </summary>
@ -915,6 +924,15 @@ namespace WebsitePanel.WebDavPortal.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to You are changing password for &apos;{0}&apos; account..
/// </summary>
public static string YouRChangingPswForFormat {
get {
return ResourceManager.GetString("YouRChangingPswForFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Zip/Postal Code.
/// </summary>

View file

@ -405,4 +405,10 @@
<data name="NewPasswordBeenSet" xml:space="preserve">
<value>Your new password has been set.</value>
</data>
<data name="PasswordFollowingRequirements" xml:space="preserve">
<value>Password must meet the following requirements:</value>
</data>
<data name="YouRChangingPswForFormat" xml:space="preserve">
<value>You are changing password for '{0}' account.</value>
</data>
</root>

View file

@ -1,9 +1,9 @@
@{
@using WebsitePanel.WebDavPortal.Resources
@using WebsitePanel.WebDavPortal.UI.Routes
@model WebsitePanel.WebDavPortal.Models.Account.PasswordResetFinalStepModel
@{
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" }))
@ -12,8 +12,14 @@
<div class="form-group">
<h3>@UI.PasswordReset</h3>
</div>
<div class="form-group">
<span>
@string.Format(UI.YouRChangingPswForFormat, Model.Login)
</span>
</div>
@Html.EditorFor(x=>x)
@Html.EditorFor(x => x.PasswordEditor)
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
@ -23,5 +29,64 @@
}
</div>
@if (Model.PasswordEditor.Settings != null)
{
<div class="form-group hidden" id="password-hint-popup">
<ul>
<li>
@string.Format(Messages.PasswordMinLengthFormat, Model.PasswordEditor.Settings.MinimumLength)
</li>
<li>
@string.Format(Messages.PasswordMaxLengthFormat, Model.PasswordEditor.Settings.MaximumLength)
</li>
@if (Model.PasswordEditor.Settings.PasswordComplexityEnabled)
{
if (Model.PasswordEditor.Settings.UppercaseLettersCount > 0)
{
<li>
@string.Format(Messages.PasswordUppercaseCountFormat, Model.PasswordEditor.Settings.UppercaseLettersCount)
</li>
}
if (Model.PasswordEditor.Settings.NumbersCount > 0)
{
<li>
@string.Format(Messages.PasswordNumbersCountFormat, Model.PasswordEditor.Settings.NumbersCount)
</li>
}
if (Model.PasswordEditor.Settings.SymbolsCount > 0)
{
<li>
@string.Format(Messages.PasswordSymbolsCountFormat, Model.PasswordEditor.Settings.SymbolsCount)
</li>
}
}
</ul>
</div>
}
@section scripts{
<script>
$(function() {
if ($(document).width() < 800) {
$('.password-input').data('placement', "bottom");
}
$(".password-input").popover({
html: true,
title: '@UI.PasswordFollowingRequirements',
content: function() {
return $('#password-hint-popup').html();
}
})
.blur(function() {
$(this).popover('hide');
});
});
</script>
}

View file

@ -8,7 +8,7 @@
<div class="form-group">
<label for="@Html.IdFor(x => x.NewPassword)" class="col-sm-2 control-label">@UI.NewPassword</label>
<div class="col-sm-10">
@Html.PasswordFor(x => x.NewPassword, new { @class = "form-control", placeholder = UI.NewPassword, maxlength })
@Html.PasswordFor(x => x.NewPassword, new { @class = "form-control password-input", placeholder = UI.NewPassword, maxlength })
@Html.Raw(HttpUtility.HtmlDecode(Html.ValidationMessageFor(m => m.NewPassword).ToHtmlString()))
</div>
</div>
@ -16,7 +16,7 @@
<div class="form-group">
<label for="@Html.IdFor(x => x.NewPasswordConfirmation)" class="col-sm-2 control-label">@UI.NewPasswordConfirmation</label>
<div class="col-sm-10">
@Html.PasswordFor(x => x.NewPasswordConfirmation, new { @class = "form-control", placeholder = UI.NewPasswordConfirmation, maxlength })
@Html.PasswordFor(x => x.NewPasswordConfirmation, new { @class = "form-control" , placeholder = UI.NewPasswordConfirmation, maxlength })
@Html.ValidationMessageFor(x => x.NewPasswordConfirmation)
</div>
</div>

View file

@ -198,6 +198,7 @@
<Compile Include="Models\AccountModel.cs" />
<Compile Include="Models\Account\PasswordChangeModel.cs" />
<Compile Include="Models\Account\PasswordResetEmailModel.cs" />
<Compile Include="Models\Account\PasswordResetFinalStepModel.cs" />
<Compile Include="Models\Account\PasswordResetSmsModel.cs" />
<Compile Include="Models\Account\UserProfile.cs" />
<Compile Include="Models\Common\AjaxModel.cs" />
@ -510,6 +511,7 @@
<EmbeddedResource Include="Resources\Messages.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Messages.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Resources\UI.resx">
<Generator>PublicResXFileCodeGenerator</Generator>

View file

@ -192,4 +192,7 @@
<data name="TwilioSettings.Text" xml:space="preserve">
<value>Twilio</value>
</data>
<data name="lblPasswordResetLinkLifeSpan.Text" xml:space="preserve">
<value>Password Reset Link Life Span (hours):</value>
</data>
</root>

View file

@ -120,6 +120,9 @@
<data name="btnResetPassoword.Text" xml:space="preserve">
<value>Send Password Reset Email</value>
</data>
<data name="chkDontSaveAsMobile.Text" xml:space="preserve">
<value>Don't save as user mobile</value>
</data>
<data name="locEmailAddress.Text" xml:space="preserve">
<value>Email:</value>
</data>

View file

@ -45,6 +45,7 @@
<asp:Localize ID="locMobile" runat="server" meta:resourcekey="locMobile"></asp:Localize></td>
<td>
<asp:TextBox runat="server" ID="txtMobile" CssClass="TextBox200" />
<asp:CheckBox ID="chkDontSaveAsMobile" runat="server" Text="Don't save as mobile" meta:resourcekey="chkDontSaveAsMobile" Checked="True" />
<asp:RequiredFieldValidator ID="valMobile" runat="server" ErrorMessage="*" ControlToValidate="txtMobile" ValidationGroup="ResetUserPassword"></asp:RequiredFieldValidator>
<asp:RegularExpressionValidator ID="regexMobileValid" runat="server" ValidationExpression="^\+?\d+$" ValidationGroup="ResetUserPassword" ControlToValidate="txtMobile" ErrorMessage="Invalid Mobile Format"></asp:RegularExpressionValidator>
</td>

View file

@ -39,11 +39,13 @@ namespace WebsitePanel.Portal.ExchangeServer
if (rbtnEmail.Checked)
{
ES.Services.Organizations.SendResetUserPasswordEmail(PanelRequest.ItemID,PanelRequest.AccountID, txtReason.Text, txtEmailAddress.Text, true);
ES.Services.Organizations.SendResetUserPasswordEmail(PanelRequest.ItemID, PanelRequest.AccountID,
txtReason.Text, txtEmailAddress.Text, true);
}
else
{
var result = ES.Services.Organizations.SendResetUserPasswordLinkSms(PanelRequest.ItemID, PanelRequest.AccountID, txtReason.Text, txtMobile.Text);
var result = ES.Services.Organizations.SendResetUserPasswordLinkSms(PanelRequest.ItemID,
PanelRequest.AccountID, txtReason.Text, txtMobile.Text);
if (!result.IsSuccess)
{
@ -51,6 +53,49 @@ namespace WebsitePanel.Portal.ExchangeServer
return;
}
if (chkDontSaveAsMobile.Checked == false)
{
OrganizationUser user = ES.Services.Organizations.GetUserGeneralSettings(PanelRequest.ItemID,
PanelRequest.AccountID);
ES.Services.Organizations.SetUserGeneralSettings(
PanelRequest.ItemID, PanelRequest.AccountID,
user.DisplayName,
string.Empty,
false,
user.Disabled,
user.Locked,
user.FirstName,
user.Initials,
user.LastName,
user.Address,
user.City,
user.State,
user.Zip,
user.Country,
user.JobTitle,
user.Company,
user.Department,
user.Office,
user.Manager == null ? null : user.Manager.AccountName,
user.BusinessPhone,
user.Fax,
user.HomePhone,
txtMobile.Text,
user.Pager,
user.WebPage,
user.Notes,
user.ExternalEmail,
user.SubscriberNumber,
user.LevelId,
user.IsVIP,
user.UserMustChangePassword);
}
}
Response.Redirect(PortalUtils.EditUrl("ItemID", PanelRequest.ItemID.ToString(),

View file

@ -147,6 +147,15 @@ namespace WebsitePanel.Portal.ExchangeServer {
/// </remarks>
protected global::System.Web.UI.WebControls.TextBox txtMobile;
/// <summary>
/// chkDontSaveAsMobile control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.CheckBox chkDontSaveAsMobile;
/// <summary>
/// valMobile control.
/// </summary>

View file

@ -92,10 +92,15 @@
<asp:CheckBox ID="chkEnablePasswordReset" runat="server" Text="Yes" meta:resourcekey="chkEnablePasswordReset" />
</td>
</tr>
<tr>
<td class="SubHead" style="width:200px;"><asp:Localize ID="lblPasswordResetLinkLifeSpan" runat="server" meta:resourcekey="lblPasswordResetLinkLifeSpan" />
<td><asp:TextBox runat="server" ID="txtPasswordResetLinkLifeSpan" Width="450px" /></td>
</tr>
<tr>
<td class="SubHead" style="width:200px;"><asp:Localize ID="lblWebdavPortalUrl" runat="server" meta:resourcekey="lblWebdavPortalUrl" />
<td><asp:TextBox runat="server" ID="txtWebdavPortalUrl" Width="450px" /></td>
</tr>
</table>
</asp:Panel>

View file

@ -166,6 +166,7 @@ namespace WebsitePanel.Portal
{
chkEnablePasswordReset.Checked = Utils.ParseBool(settings[WSP.SystemSettings.WEBDAV_PASSWORD_RESET_ENABLED_KEY], false);
txtWebdavPortalUrl.Text = settings[WEBDAV_PORTAL_URL];
txtPasswordResetLinkLifeSpan.Text = settings[WSP.SystemSettings.WEBDAV_PASSWORD_RESET_LINK_LIFE_SPAN];
}
// Twilio portal
@ -262,6 +263,7 @@ namespace WebsitePanel.Portal
settings = new WSP.SystemSettings();
settings[WEBDAV_PORTAL_URL] = txtWebdavPortalUrl.Text;
settings[WSP.SystemSettings.WEBDAV_PASSWORD_RESET_ENABLED_KEY] = chkEnablePasswordReset.Checked.ToString();
settings[WSP.SystemSettings.WEBDAV_PASSWORD_RESET_LINK_LIFE_SPAN] = txtPasswordResetLinkLifeSpan.Text;
result = ES.Services.System.SetSystemSettings(WSP.SystemSettings.WEBDAV_PORTAL_SETTINGS, settings);
if (result < 0)

View file

@ -264,6 +264,24 @@ namespace WebsitePanel.Portal {
/// </remarks>
protected global::System.Web.UI.WebControls.CheckBox chkEnablePasswordReset;
/// <summary>
/// lblPasswordResetLinkLifeSpan control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Localize lblPasswordResetLinkLifeSpan;
/// <summary>
/// txtPasswordResetLinkLifeSpan control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.TextBox txtPasswordResetLinkLifeSpan;
/// <summary>
/// lblWebdavPortalUrl control.
/// </summary>