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); provider.ChangeApproval(id, false);
return RedirectToAction("Index"); 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> <td>
Last Login Time Last Login Time
</td> </td>
<td>
Last Lockout Time
</td>
<th style="width:108px;"></th>
<th style="width:108px;"></th> <th style="width:108px;"></th>
</tr> </tr>
@ -32,6 +36,9 @@
<td> <td>
@Html.DisplayFor(modelItem => mu.LastLoginDate) @Html.DisplayFor(modelItem => mu.LastLoginDate)
</td> </td>
<td>
@Html.DisplayFor(modelItem => mu.LastLockoutDate)
</td>
<td class="text-right"> <td class="text-right">
@if (mu.IsApproved) @if (mu.IsApproved)
{ {
@ -42,6 +49,16 @@
@Html.ActionLink("Approve", "approve", new { id = mu.ProviderUserKey }, new { @class = "btn btn-success", style = "width:90px;" }) @Html.ActionLink("Approve", "approve", new { id = mu.ProviderUserKey }, new { @class = "btn btn-success", style = "width:90px;" })
} }
</td> </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> </tr>
} }

View File

@ -1,11 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Web;
using System.Web.Security; using System.Web.Security;
using NServiceKit.DataAnnotations; using NServiceKit.DataAnnotations;
using NServiceKit.DesignPatterns.Model; using NServiceKit.DesignPatterns.Model;
@ -16,6 +15,13 @@ namespace BuildFeed.Auth
{ {
public class RedisMembershipProvider : MembershipProvider 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 public override string ApplicationName
{ {
get { return ""; } get { return ""; }
@ -24,7 +30,7 @@ public override string ApplicationName
public override bool EnablePasswordReset public override bool EnablePasswordReset
{ {
get { return true; } get { return _enablePasswordReset; }
} }
public override bool EnablePasswordRetrieval public override bool EnablePasswordRetrieval
@ -34,22 +40,22 @@ public override bool EnablePasswordRetrieval
public override int MaxInvalidPasswordAttempts public override int MaxInvalidPasswordAttempts
{ {
get { return 5; } get { return _maxInvalidPasswordAttempts; }
} }
public override int MinRequiredNonAlphanumericCharacters public override int MinRequiredNonAlphanumericCharacters
{ {
get { return 1; } get { return _minRequiredNonAlphanumericCharacters; }
} }
public override int MinRequiredPasswordLength public override int MinRequiredPasswordLength
{ {
get { return 12; } get { return _minRequriedPasswordLength; }
} }
public override int PasswordAttemptWindow public override int PasswordAttemptWindow
{ {
get { return 60; } get { return _passwordAttemptWindow; }
} }
public override MembershipPasswordFormat PasswordFormat public override MembershipPasswordFormat PasswordFormat
@ -69,7 +75,24 @@ public override bool RequiresQuestionAndAnswer
public override bool RequiresUniqueEmail 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) 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) public override bool UnlockUser(string userName)
{ {
using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) 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 client = rClient.As<RedisMember>();
var rm = client.GetAll().SingleOrDefault(m => m.UserName.ToLower() == username.ToLower()); 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; return false;
} }
@ -355,15 +404,57 @@ public override bool ValidateUser(string username, string password)
isFail |= (hash[i] != rm.PassHash[i]); 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; rm.LastLoginDate = DateTime.Now;
client.Store(rm); rm.LockoutWindowStart = DateTime.MinValue;
rm.LockoutWindowAttempts = 0;
} }
client.Store(rm);
return !isFail; 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] [DataObject]
@ -398,5 +489,8 @@ public class RedisMember : IHasId<Guid>
public DateTime LastActivityDate { get; set; } public DateTime LastActivityDate { get; set; }
public DateTime LastLockoutDate { get; set; } public DateTime LastLockoutDate { get; set; }
public DateTime LastLoginDate { 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) 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"]; string returnUrl = string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]) ? "/" : Request.QueryString["ReturnUrl"];

View File

@ -18,6 +18,22 @@
<h2>@Model.FullBuildString</h2> <h2>@Model.FullBuildString</h2>
<div class="form-horizontal form-details"> <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"> <div class="form-group">
@Html.LabelFor(model => model.MajorVersion, new { @class = "control-label col-sm-2" }) @Html.LabelFor(model => model.MajorVersion, new { @class = "control-label col-sm-2" })
<div class="col-sm-10"> <div class="col-sm-10">
@ -154,14 +170,6 @@
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<a href="/" class="btn btn-info">Return to Listing</a> <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> </div>
</div> </div>

View File

@ -29,6 +29,10 @@
--> -->
</system.web> </system.web>
<system.webServer> <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"> <rewrite xdt:Transform="Insert">
<rules> <rules>
<rule name="CanonicalHost" stopProcessing="true"> <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" /> <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers> </handlers>
<modules> <modules>
<remove name="ApplicationInsightsWebTracking" />
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Extensibility.Web.RequestTracking.WebRequestTrackingModule, Microsoft.ApplicationInsights.Extensibility.Web" preCondition="managedHandler" />
</modules> </modules>
<urlCompression doDynamicCompression="true" /> <urlCompression doDynamicCompression="true" />
</system.webServer> </system.webServer>