Membership Provider improvements, tweaks for me

This commit is contained in:
Thomas Hounsell 2014-12-04 21:42:47 +00:00
parent b35447fdf7
commit 18740b6336
7 changed files with 164 additions and 23 deletions

View File

@ -30,5 +30,19 @@ public ActionResult unapprove(Guid id)
provider.ChangeApproval(id, false);
return RedirectToAction("Index");
}
public ActionResult @lock(Guid id)
{
var provider = (Membership.Provider as RedisMembershipProvider);
provider.ChangeLockStatus(id, true);
return RedirectToAction("Index");
}
public ActionResult unlock(Guid id)
{
var provider = (Membership.Provider as RedisMembershipProvider);
provider.ChangeLockStatus(id, false);
return RedirectToAction("Index");
}
}
}

View File

@ -17,6 +17,10 @@
<td>
Last Login Time
</td>
<td>
Last Lockout Time
</td>
<th style="width:108px;"></th>
<th style="width:108px;"></th>
</tr>
@ -32,16 +36,29 @@
<td>
@Html.DisplayFor(modelItem => mu.LastLoginDate)
</td>
<td>
@Html.DisplayFor(modelItem => mu.LastLockoutDate)
</td>
<td class="text-right">
@if (mu.IsApproved)
{
@Html.ActionLink("Unapprove", "unapprove", new { id = mu.ProviderUserKey }, new { @class = "btn btn-danger", style = "width:90px;" })
}
}
else
{
@Html.ActionLink("Approve", "approve", new { id = mu.ProviderUserKey }, new { @class = "btn btn-success", style = "width:90px;" })
}
</td>
<td class="text-right">
@if (!mu.IsLockedOut)
{
@Html.ActionLink("Lock", "lock", new { id = mu.ProviderUserKey }, new { @class = "btn btn-danger", style = "width:90px;" })
}
else
{
@Html.ActionLink("Unlock", "unlock", new { id = mu.ProviderUserKey }, new { @class = "btn btn-success", style = "width:90px;" })
}
</td>
</tr>
}

View File

@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Security;
using NServiceKit.DataAnnotations;
using NServiceKit.DesignPatterns.Model;
@ -16,6 +15,13 @@ namespace BuildFeed.Auth
{
public class RedisMembershipProvider : MembershipProvider
{
private bool _enablePasswordReset = true;
private int _maxInvalidPasswordAttempts = 5;
private int _minRequiredNonAlphanumericCharacters = 1;
private int _minRequriedPasswordLength = 12;
private int _passwordAttemptWindow = 60;
private bool _requiresUniqueEmail = true;
public override string ApplicationName
{
get { return ""; }
@ -24,7 +30,7 @@ public override string ApplicationName
public override bool EnablePasswordReset
{
get { return true; }
get { return _enablePasswordReset; }
}
public override bool EnablePasswordRetrieval
@ -34,22 +40,22 @@ public override bool EnablePasswordRetrieval
public override int MaxInvalidPasswordAttempts
{
get { return 5; }
get { return _maxInvalidPasswordAttempts; }
}
public override int MinRequiredNonAlphanumericCharacters
{
get { return 1; }
get { return _minRequiredNonAlphanumericCharacters; }
}
public override int MinRequiredPasswordLength
{
get { return 12; }
get { return _minRequriedPasswordLength; }
}
public override int PasswordAttemptWindow
{
get { return 60; }
get { return _passwordAttemptWindow; }
}
public override MembershipPasswordFormat PasswordFormat
@ -69,7 +75,24 @@ public override bool RequiresQuestionAndAnswer
public override bool RequiresUniqueEmail
{
get { return true; }
get { return _requiresUniqueEmail; }
}
public override void Initialize(string name, NameValueCollection config)
{
if (config == null)
{
throw new ArgumentNullException("config");
}
base.Initialize(name, config);
_enablePasswordReset = tryReadBool(config["enablePasswordReset"], _enablePasswordReset);
_maxInvalidPasswordAttempts = tryReadInt(config["maxInvalidPasswordAttempts"], _maxInvalidPasswordAttempts);
_minRequiredNonAlphanumericCharacters = tryReadInt(config["minRequiredNonAlphanumericCharacters"], _minRequiredNonAlphanumericCharacters);
_minRequriedPasswordLength = tryReadInt(config["minRequriedPasswordLength"], _minRequriedPasswordLength);
_passwordAttemptWindow = tryReadInt(config["passwordAttemptWindow"], _passwordAttemptWindow);
_requiresUniqueEmail = tryReadBool(config["requiresUniqueEmail"], _requiresUniqueEmail);
}
public override bool ChangePassword(string username, string oldPassword, string newPassword)
@ -313,6 +336,32 @@ public void ChangeApproval(Guid Id, bool newStatus)
}
}
public void ChangeLockStatus(Guid Id, bool newStatus)
{
using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database))
{
var client = rClient.As<RedisMember>();
var rm = client.GetById(Id);
if (rm != null)
{
rm.IsLockedOut = newStatus;
if (newStatus)
{
rm.LastLockoutDate = DateTime.Now;
}
else
{
rm.LockoutWindowAttempts = 0;
rm.LockoutWindowStart = DateTime.MinValue;
}
client.Store(rm);
}
}
}
public override bool UnlockUser(string userName)
{
using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database))
@ -340,7 +389,7 @@ public override bool ValidateUser(string username, string password)
var client = rClient.As<RedisMember>();
var rm = client.GetAll().SingleOrDefault(m => m.UserName.ToLower() == username.ToLower());
if (rm == null || !rm.IsApproved)
if (rm == null || !(rm.IsApproved && !rm.IsLockedOut))
{
return false;
}
@ -355,15 +404,57 @@ public override bool ValidateUser(string username, string password)
isFail |= (hash[i] != rm.PassHash[i]);
}
if(!isFail)
if (isFail)
{
if (rm.LockoutWindowStart == DateTime.MinValue)
{
rm.LockoutWindowStart = DateTime.Now;
rm.LockoutWindowAttempts = 1;
}
else
{
if (rm.LockoutWindowStart.AddMinutes(PasswordAttemptWindow) > DateTime.Now)
{
// still within window
rm.LockoutWindowAttempts++;
if (rm.LockoutWindowAttempts >= MaxInvalidPasswordAttempts)
{
rm.IsLockedOut = true;
}
}
else
{
// outside of window, reset
rm.LockoutWindowStart = DateTime.Now;
rm.LockoutWindowAttempts = 1;
}
}
}
else
{
rm.LastLoginDate = DateTime.Now;
client.Store(rm);
rm.LockoutWindowStart = DateTime.MinValue;
rm.LockoutWindowAttempts = 0;
}
client.Store(rm);
return !isFail;
}
}
private static bool tryReadBool(string config, bool defaultValue)
{
bool temp = false;
bool success = bool.TryParse(config, out temp);
return success ? temp : defaultValue;
}
private static int tryReadInt(string config, int defaultValue)
{
int temp = 0;
bool success = int.TryParse(config, out temp);
return success ? temp : defaultValue;
}
}
[DataObject]
@ -398,5 +489,8 @@ public class RedisMember : IHasId<Guid>
public DateTime LastActivityDate { get; set; }
public DateTime LastLockoutDate { get; set; }
public DateTime LastLoginDate { get; set; }
public DateTime LockoutWindowStart { get; set; }
public int LockoutWindowAttempts { get; set; }
}
}

View File

@ -33,7 +33,13 @@ public ActionResult login(LoginUser ru)
if (isAuthenticated)
{
FormsAuthentication.SetAuthCookie(ru.UserName, ru.RememberMe);
int expiryLength = ru.RememberMe ? 129600 : 60;
var ticket = new FormsAuthenticationTicket(ru.UserName, true, expiryLength);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookieTicket = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookieTicket.Expires = DateTime.Now.AddMinutes(expiryLength);
cookieTicket.Path = FormsAuthentication.FormsCookiePath;
Response.Cookies.Add(cookieTicket);
string returnUrl = string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]) ? "/" : Request.QueryString["ReturnUrl"];

View File

@ -18,6 +18,22 @@
<h2>@Model.FullBuildString</h2>
<div class="form-horizontal form-details">
@if (User.Identity.IsAuthenticated)
{
<div class="form-group">
<label class="control-label col-sm-2">Editor Actions</label>
<div class="col-sm-10">
<p class="form-control-static">
@Html.ActionLink("Edit", "edit", new { id = Model.Id }, new { @class = "btn btn-default btn-xs" })
@if (User.Identity.Name == "hounsell")
{
@Html.ActionLink("Delete", "delete", new { id = Model.Id }, new { @class = "btn btn-danger btn-xs" })
}
</p>
</div>
</div>
}
<div class="form-group">
@Html.LabelFor(model => model.MajorVersion, new { @class = "control-label col-sm-2" })
<div class="col-sm-10">
@ -154,14 +170,6 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<a href="/" class="btn btn-info">Return to Listing</a>
@if (User.Identity.IsAuthenticated)
{
@Html.ActionLink("Edit", "edit", new { id = Model.Id }, new { @class = "btn btn-default" })
}
@if (User.Identity.Name == "hounsell")
{
@Html.ActionLink("Delete", "delete", new { id = Model.Id }, new { @class = "btn btn-danger" })
}
</div>
</div>
</div>

View File

@ -29,6 +29,10 @@
-->
</system.web>
<system.webServer>
<modules>
<remove name="ApplicationInsightsWebTracking" xdt:Transform="Insert" />
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Extensibility.Web.RequestTracking.WebRequestTrackingModule, Microsoft.ApplicationInsights.Extensibility.Web" preCondition="managedHandler" xdt:Transform="Insert" />
</modules>
<rewrite xdt:Transform="Insert">
<rules>
<rule name="CanonicalHost" stopProcessing="true">

View File

@ -71,8 +71,6 @@
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
<modules>
<remove name="ApplicationInsightsWebTracking" />
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Extensibility.Web.RequestTracking.WebRequestTrackingModule, Microsoft.ApplicationInsights.Extensibility.Web" preCondition="managedHandler" />
</modules>
<urlCompression doDynamicCompression="true" />
</system.webServer>