diff --git a/Areas/admin/Controllers/usersController.cs b/Areas/admin/Controllers/usersController.cs
index f07bb10..69cde78 100644
--- a/Areas/admin/Controllers/usersController.cs
+++ b/Areas/admin/Controllers/usersController.cs
@@ -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");
+ }
}
}
\ No newline at end of file
diff --git a/Areas/admin/Views/users/index.cshtml b/Areas/admin/Views/users/index.cshtml
index dcb2a53..ddcc3fa 100644
--- a/Areas/admin/Views/users/index.cshtml
+++ b/Areas/admin/Views/users/index.cshtml
@@ -17,6 +17,10 @@
Last Login Time
|
+
+ Last Lockout Time
+ |
+ |
|
@@ -32,16 +36,29 @@
@Html.DisplayFor(modelItem => mu.LastLoginDate)
|
+
+ @Html.DisplayFor(modelItem => mu.LastLockoutDate)
+ |
@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;" })
}
|
+
+ @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;" })
+ }
+ |
}
diff --git a/Auth/RedisMembershipProvider.cs b/Auth/RedisMembershipProvider.cs
index 47f3073..e9181dd 100644
--- a/Auth/RedisMembershipProvider.cs
+++ b/Auth/RedisMembershipProvider.cs
@@ -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();
+ 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();
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
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; }
}
}
\ No newline at end of file
diff --git a/Controllers/supportController.cs b/Controllers/supportController.cs
index 589917d..084fe64 100644
--- a/Controllers/supportController.cs
+++ b/Controllers/supportController.cs
@@ -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"];
diff --git a/Views/build/info.cshtml b/Views/build/info.cshtml
index e22065e..a99e7c7 100644
--- a/Views/build/info.cshtml
+++ b/Views/build/info.cshtml
@@ -18,6 +18,22 @@
@Model.FullBuildString