I'm having a problem with Web API 2 (.net 4.5.1) in that it seems to ignore PATCH requests where the property is an integer, but processes other types without a problem (I've tested string and decimal).
I’ve setup an unsecured test API with a 'products' controller at http://playapi.azurewebsites.net/api/products. If you do a GET to that URL, you’ll get something like this product back:
{"Id": 1,"Name": "Xbox One","Category": "gaming","Price": 300,"Stock": 5}
‘Name’ and ‘Category’ are both strings, ‘Price’ is a Decimal and ‘Stock’ is an Integer.
If you send these requests, they both work (You’ll get a 200/OK with the updated entity):
PATCH, http://playapi.azurewebsites.net/api/products/1 with {"Price": 600.00}
PATCH, http://playapi.azurewebsites.net/api/products/1 with
{"Category": "Electronics"}
However, if you send this, it returns 200/OK, but does not make the update and the stock remains at the original value
PATCH, http://playapi.azurewebsites.net/api/products/1 with
{"Stock": 4}
My controller code is fairly standard boiler plate code (from the scaffolded ODATA controller but moved into a standard API controller):
// PATCH: api/Products/5
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> PatchOrder(int id, Delta<Product> patch)
{
Validate(patch.GetEntity());
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var item = await db.Products.FindAsync(id);
if (item == null)
{
return NotFound();
}
patch.Patch(item);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return Ok(item);
}
My model for 'Product' is as follows:
namespace PlayAPI.Models
{
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public double Price { get; set; }
public int Stock { get; set; }
}
}
When I debug the controller, I see that the ‘patch’ object has a _changedProperties collection which has no items in it when I do an integer request, but when I do any other kind of request it has the key that I changed.
Should web API support PATCH requests for integer properties? If so, do I need to do anything special on the server or client to make it work?
As a quick fix, Change the int to an Int64 on PlayAPI.Models.Product.
public Int64 Stock { get; set; }
It's my understanding that The Delta object used to patch the existing object doesn’t use JSON.net to convert and is silently throwing an Invalid cast exception when it parses JSON and then compares to the existing object from your database. You can read more about the bug over here: http://aspnetwebstack.codeplex.com/workitem/777
If you can't actually change the data type successfully, there may be a decent hack fix that you can use. Just attached unreadable data into the query string.
Here's a function you can call from within your Patch functions. As long as you aren't using the query string parameters specifically named what it's looking for, you should be just fine.
/// <summary>
/// Tries to attach additional parameters from the query string onto the delta object.
/// This uses the parameters extraInt32 and extraInt16, which can be used multiple times.
/// The parameter format is "PropertyName|Integer"
/// <para>Example: ?extraInt32=Prop1|123&extraInt16=Prop2|88&extraInt32=Prop3|null</para>
/// </summary>
[NonAction]
protected void SetAdditionalPatchIntegers<TEntity>(Delta<TEntity> deltaEntity, bool allowNull = true)
{
var queryParameters = Request.GetQueryNameValuePairs();
foreach (var param in queryParameters.Where(pair =>
pair.Key == "extraInt32" ||
pair.Key == "extraInt16"))
{
if (param.Value.Count(v => v == '|') != 1)
continue;
var splitParam = param.Value.Split('|');
if (allowNull &&
(String.IsNullOrWhiteSpace(splitParam[1]) ||
splitParam[1].Equals("null", StringComparison.OrdinalIgnoreCase)))
{
deltaEntity.TrySetPropertyValue(splitParam[0], null);
continue;
}
if (param.Key == "extraInt32")
{
int extraInt;
if (Int32.TryParse(splitParam[1], out extraInt))
{
deltaEntity.TrySetPropertyValue(splitParam[0], extraInt);
}
}
if (param.Key == "extraInt16")
{
short extraShort;
if (Int16.TryParse(splitParam[1], out extraShort))
{
deltaEntity.TrySetPropertyValue(splitParam[0], extraShort);
}
}
}
}
I really hate that there isn't a better answer, but at least something can be done about it.
Related
I have a .NET Core 6.0 project that I use StackExchange.Redis.
First, I am wondering if I can filter the value coming with Key-Value pair.
When I get the key, I get all the values and after that I am filtering them.
Is there anyway to filter values before getting them all or I have to filter it after getting all the values ?
-- TestModel2.cs
public class TestModel2
{
public List<Product> Products { get; set; }
public List<Category> Categories { get; set; }
}
-- RedisCacheService.cs
public async Task<T> GetAsync<T>(string key) where T : class
{
string value = await _client.GetDatabase().StringGetAsync(key);
return value.ToObject<T>();
}
--ToObject
public static T ToObject<T>(this string value) where T : class
{
return string.IsNullOrEmpty(value) ? null : JsonConvert.DeserializeObject<T>(value);
}
--CacheController.cs
[HttpGet]
[Route("GetProductsByCategoryId")]
public async Task<IActionResult> GetProductsByCategoryId(int id)
{
var models = await _cacheService.GetAsync<List<TestModel2>>("models3");
if (models != null && models?.Count() > 0)
{
try
{
var model = models[0].Products.Where(x => x.CategoryId == id);
if (model != null)
{
return Ok(model);
}
}
catch (Exception)
{
return StatusCode(500);
}
}
return BadRequest("Not Found");
}
If you use a single string, then no: redis doesn't have inbuilt filtering commands (unless you use something like RedisJSON on the server, but that isn't part of core redis). Redis doesn't have rich columnar filtering like you might find in, say, a SQL database. The idea is that you create your own explicit indexing using redis primitives. Storing all the data in a single string and fetching the entire thing out each time is not optimal.
I also found after searching about how to filter and something like that, RediSearch also can be used and integrated with ASP.NET Core Project...
If you are using Docker, RedisLabs/redismod is useful for it...
I am using webapi2. I have a property in model is start date whose datatype is datetime. I want to pass the date as "dd-mm-yyyy" format. But if i send, i am getting 400 bad request. Could you please help me out. Note, I am using Fluent validation for the model validation.
public class Item
{
public DateTime? StartDate { get; set; }
public string Id { get; set; }
}
I want to pass the date as "dd-mm-yyyy"`
You have 3 options.
Option ISO8601
Don't pass it as "dd-mm-yyyy". Pass it instead in ISO8601 format (yyyy-MM-dd). That is the correct way to serialize DateTimes to string and also for then communicating that string representation between tiers. This format is a standard, widely used, unambiguous, and almost all frameworks that I am aware of have built in mechanisms for outputting DateTimes to that format and parsing them from that format.
Displaying a DateTime formatted as "dd-mm-yyyy" is a presentation layer concern and it should stay there and not "bleed" into the other application layers.
Option Formatters
Use custom code, like a Json Converte or an ActionFilterAttribute, to read the incoming DateTime.
Option String
Accept a string parameter instead and handle your own parsing inside the controller's method.
I honestly do not recommend the last 2 options. Instead use ISO8601: a standard, unambiguous, widely accepted means of communicating a DateTime.
I have created a custom value provider factory and am using the default model binding.
public class OrderValueProviderFactory<T> : ValueProviderFactory where T : class
{
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
var querystring = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key.ToLower(), x => x.Value);
return new OrderValueProvider<T>(querystring);
}
}
public class OrderValueProvider<T> : IValueProvider
{
readonly Dictionary<string, string> querystring;
public OrderValueProvider(Dictionary<string, string> _querystring)
{
querystring = _querystring;
}
public bool ContainsPrefix(string prefix)
{
return true;
}
public ValueProviderResult GetValue(string key)
{
T obj = (T)Activator.CreateInstance(typeof(T));
PropertyInfo[] properties = typeof(T).GetProperties();
foreach (var property in properties)
{
if (property.PropertyType == typeof(string))
{
property.SetValue(obj, querystring.GetStringValue(property.Name.ToLower()));
}
else if (property.PropertyType == typeof(DateTime?))
{
property.SetValue(obj, querystring.GetDateTimeValue(property.Name.ToLower()));
}
else if (property.PropertyType == typeof(int))
{
property.SetValue(obj, querystring.GetIntValue(property.Name.ToLower()));
}
}
return new ValueProviderResult(obj, "", CultureInfo.InvariantCulture);
}
}
I am looking at implementing a logging mechanism in a site of mine, I wish to do basic user action logging. I don't want to log every single button they click on, but I do want to log actions they do which makes changes.
Are there any libraries or articles / tutorials out there which can help me implement a good and efficient logging mechanism for my asp.net site. Im not sure if there are any changes in MVC5 that might come in use for logging as I know user Authentication and Authorization has changed a fair amount from 4 to 5.
I'm sure that there is a dynamic library out there that will work in many different situations.
Nice to haves:
Async capability
Scalable
Simple to use
I'm thinking along the lines of creating a custom filter or attribute that then logs the suers action, but that's just my Idea, Im here to ask what the standard / industry way to do it is.
There isn't an industry standard.
I've used filters or I've overridden the "onActionExecuting" method on the base controller class to record controller / action events.
EDIT ::
Trying to be more helpful but this is really vague.
If you're worried about errors and things like that use elmah.
For other logging use Nlog or Log4Net.
If you're trying to do extra logging like auditing or something like that you can use any combination of those, or something custom. In my site I created a table that stores every click on the site by creating an object sort of like this :
public class audit
{
public int ID { get; set; }
public DateTime AuditDate { get; set; }
public string ControllerName { get; set; }
public string ActionName { get; set; }
public Dictionary<string, object> values
}
In my base constructor, I overrode the OnActionExecuting event :
protected override void OnActionExecuting(ActionExecutingContext ctx)
{
checkForLogging(ctx);
//do not omit this line
base.OnActionExecuting(ctx);
}
Lets say I want to log all Get Requests using my new audit object
private void checkForLogging(ActionExecutingContext ctx)
{
//we leave logging of posts up to the actual methods because they're more complex...
if (ctx.HttpContext.Request.RequestType == "GET")
{
logging(ctx.ActionDescriptor.ActionName, ctx.ActionDescriptor.ControllerDescriptor.ControllerName, ctx.ActionParameters);
}
}
Thats all the info I need to fill my logging object with the action name, the controller name and all the params passed into the method. You can either save this to a db, or a logfile or whatever you want really.
The point is just its a pretty big thing. This is just one way to do it and it may or may not help you. Maybe define a bit more what exactly you want to log and when you want to do it?
You can create a custom attribute and decorate methods with it and then check if that attribute is present when the OnActionExecuting method fires. You can then get that filter if present and read from it and use that to drive your logging if you want...
Maybe this example will help.
My focus on logging is in the CREATE, EDIT, DELETE actions.
I am using MVC 5 Code-first EF 6.1 (VS 2013) ,
and for this example I are referring to the Create action for an entity called "WorkFlow"
I actually view these logs from SSRS, but you could add a controller and Views for WriteUsageLog and view them from the MVC application
MODEL: Create a MODEL Entity called "WriteUsageLog" which will be where the log records are kept
CONTROLLER: Extract, or refactor, the HttpPost overload of the "Create" action from the WorkFlowController into a Partial Class called "WorkFlowController" (These partials are to avoid being deleted and rebuilt when I use the wizard to create Controllers)
Other Classes in the CONTROLLER folder: Then there are some helper functions that are required in a class called "General_Object_Extensions" and "General_ActiveDirectory_Extensions" (NOTE: these are not really 'extensions')
Add the following line to the DBContext:
public DbSet WriteUsageLogs { get; set; }
The advantage of this example is:
I am recording the following for the record:
User Name from Active Directory
The DateTime that the log record is being created
The computer name
And a Note that consists of the values for all the entity properties
I am recording the log in a table from which I can access it either using an MVC controller, or preferably from SQL Server Report Server. Where I can monitor all my MVC applications
/Models/WriteUsageLog.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MileageReimbursement.Models
{
public class WriteUsageLog
{
public WriteUsageLog()
{
this.DateTimeCreated = DateTime.Now; // auto-populates DateTimeCreated field
}
[Key]
public int WriteUsageLogID { get; set; }
[Column(TypeName = "nvarchar(max)")]
public string Note { get; set; }
public string UserLogIn { get; set; }
public string ComputerName { get; set; }
public DateTime DateTimeCreated { get; private set; } //private set to for auto-populates DateTimeCreated field
}
}
/Controllers/ControllerPartials.cs
using System.Linq;
using System.Web.Mvc;
using MileageReimbursement.Models;
//These partials are to avoid be deleted and rebuilt when I use the wizard to create Controllers
namespace MileageReimbursement.Controllers
{
public partial class WorkFlowController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "whatever")] WorkFlow workFlow)
{
...
if (ModelState.IsValid)
{
db.WorkFlows.Add(workFlow);
db.SaveChanges();
//===================================================================================================================
string sX = workFlow.GetStringWith_RecordProperties();
//===================================================================================================================
var logRecord = new WriteUsageLog();
logRecord.Note = "New WorkFlow Record Added - " + sX;
logRecord.UserLogIn = General_ActiveDirectory_Extensions.fn_sUser();
string IP = Request.UserHostName;
logRecord.ComputerName = General_functions.fn_ComputerName(IP);
db.WriteUsageLogs.Add(logRecord);
db.SaveChanges();
//===================================================================================================================
return RedirectToAction("Index");
}
else // OR the user is directed back to the validation error messages and given an opportunity to correct them
{
...
return View(workFlow); //This sends the user back to the CREATE view to deal with their errors
}
}
}
}
/Controllers/ControllerExtensions.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace MileageReimbursement.Controllers
{
public static class General_ActiveDirectory_Extensions
{
public static string fn_sUser()
{
char cX = '\\';
string sUser = General_functions.fn_ReturnPortionOfStringAfterLastOccuranceOfACharacter(HttpContext.Current.User.Identity.Name, cX);
return sUser; //returns just the short logon name Example for 'accessiicarewnc\ggarson', it returns 'ggarson'
}
} //General_ActiveDirectory_Extensions
public static class General_Object_Extensions
{
public static string GetStringWith_RecordProperties(this object Record)
{
string sX = null;
Dictionary<string, object> _record = GetDictionary_WithPropertiesForOneRecord(Record);
int iPropertyCounter = 0;
foreach (var KeyValuePair in _record)
{
iPropertyCounter += 1;
object thePropertyValue = _record[KeyValuePair.Key];
if (thePropertyValue != null)
{
sX = sX + iPropertyCounter + ") Property: {" + KeyValuePair.Key + "} = [" + thePropertyValue + "] \r\n";
}
else
{
sX = sX + iPropertyCounter + ") Property: {" + KeyValuePair.Key + "} = [{NULL}] \r\n";
}
}
return sX;
}
public static Dictionary<string, object> GetDictionary_WithPropertiesForOneRecord(object atype)
{
if (atype == null) return new Dictionary<string, object>();
Type t = atype.GetType();
PropertyInfo[] props = t.GetProperties();
Dictionary<string, object> dict = new Dictionary<string, object>();
foreach (PropertyInfo prp in props)
{
object value = prp.GetValue(atype, new object[] { });
dict.Add(prp.Name, value);
}
return dict;
}
} //General_Object_Extensions
public static class General_functions
{
public static string fn_ComputerName(string IP)
{
//USAGE
//From: http://stackoverflow.com/questions/1444592/determine-clients-computer-name
//string IP = Request.UserHostName;
//string compName = CompNameHelper.DetermineCompName(IP);
IPAddress myIP = IPAddress.Parse(IP);
IPHostEntry GetIPHost = Dns.GetHostEntry(myIP);
List<string> compName = GetIPHost.HostName.ToString().Split('.').ToList();
return compName.First();
}
static public string fn_ReturnPortionOfStringAfterLastOccuranceOfACharacter(string strInput, char cBreakCharacter)
{
// NOTE: for path backslash "/", set cBreakCharacter = '\\'
string strX = null;
//1] how long is the string
int iStrLenth = strInput.Length;
//2] How far from the end does the last occurance of the character occur
int iLenthFromTheLeftOfTheLastOccurance = strInput.LastIndexOf(cBreakCharacter);
int iLenthFromTheRightToUse = 0;
iLenthFromTheRightToUse = iStrLenth - iLenthFromTheLeftOfTheLastOccurance;
//3] Get the Portion of the string, that occurs after the last occurance of the character
strX = fn_ReturnLastXLettersOfString(iLenthFromTheRightToUse, strInput);
return strX;
}
static private string fn_ReturnLastXLettersOfString(int iNoOfCharToReturn, string strIn)
{
int iLenth = 0;
string strX = null;
int iNoOfCharacters = iNoOfCharToReturn;
iLenth = strIn.Length;
if (iLenth >= iNoOfCharacters)
{
strX = strIn.Substring(iLenth - iNoOfCharacters + 1);
}
else
{
strX = strIn;
}
return strX;
}
} //General_functions
}
I would agree that Log4Net and NLog seem to be the two most commonly used products on the different projects I have been a member.
If you are looking for a great tool that you can use for logging, error handling and anything else where AOP would be beneficial I would highly recommend PostSharp (http://www.postsharp.net/). You set your logging/error handling up centrally and then just decorate methods. It is a well documented and supported product. They have a community license, which is free - and it is free for individuals. They also have professional and ultimate versions of the products, which would make more sense if you're using it as a team.
I don't work at PostSharp :-) I've just used it in the past and really like it.
I'm trying to do some very simple request throttling on my ASP.NET web project. Currently I'm not interested in globally throttling requests against DOS attacks, but would like to artificially delay the reponse to all login attempts, just to make dictionary attacks a bit harder to do (more or less like Jeff Atwood outlined here).
How would you implement it? The näive way of doing it would be - I suppose - to simply call
Thread.Sleep();
somewhere during the request. Suggestions? :)
I got the same idea as you on how to improve the security of a login screen (and password reset screens). I'm going to implement this for my project and I'll share my story with you.
Requirements
My requirements are in following points:
Do not block individual users just because someone is trying to hack in
My user names are very easy to guess because they follow a certain pattern (and I do not like security by obscurity)
Do not waste server resources by sleeping on too many requests, the queue would overflow eventually and requests would start timing out
Provide a swift service to most users 99% of the time
Eliminate brute force attacks on the login screen
Handle distributed attacks as well
Needs to be reasonably thread-safe
Plan
So we shall have a list of failed attempts and their time stamp. Every time we have a login attempt, we'll check this list and the more there are failed attempts, the longer it will take to login. Each time we'll prune old entries by their time stamp. Beyond a certain threshold, no logins will be allowed and all login requests will be failed immediately (attack emergency shut-down).
We do not stop with the automatic protection. A notification should be sent to admins in case of the emergency shut-down so that the incident can be investigated and reparation measures can be taken. Our logs should hold a firm record of the failed attempts including the time, user name and source IP address for investigation.
The plan is to implement this as a statically declared queue, where failed attempts enqueue and old entries dequeue. the length of the queue is our indicator of severity. When I've got the code ready, I'll update the answer. I might include Keltex's suggestion too - releasing the response quickly and completing the login with another request.
Update: There is two things missing:
The redirect of the response to a wait page not to clog the request queue and that is a little biggie obviously. We need to give the user a token to check back later with another request. This could be another security hole so we need to be extremely cautious about handling this. Or just drop that Thread.Sleap(xxx) in the Action method :)
The IP, dooh, next time...
Let's see if we can get through that eventually...
What's done
ASP.NET page
ASP.NET UI Page should have minimum hassle, then we get an instance of a Gate like this:
static private LoginGate Gate = SecurityDelayManager.Instance.GetGate<LoginGate>();
And after login (or password reset) attempt, call:
SecurityDelayManager.Instance.Check(Gate, Gate.CreateLoginAttempt(success, UserName));
ASP.NET handling code
The LoginGate is implemented inside the AppCode of the ASP.NET project so it has access to all the front-end goodies. It implements the interface IGate which is used by the backend SecurityDelayManager instance. The Action method needs to be completed with wait redirection.
public class LoginGate : SecurityDelayManager.IGate
{
#region Static
static Guid myID = new Guid("81e19a1d-a8ec-4476-a187-3130361a9006");
static TimeSpan myTF = TimeSpan.FromHours(24);
#endregion
#region Private Types
class LoginAttempt : Attempt { }
class PasswordResetAttempt : Attempt { }
class PasswordResetRequestAttempt : Attempt { }
abstract class Attempt : SecurityDelayManager.IAttempt
{
public bool Successful { get; set; }
public DateTime Time { get; set; }
public String UserName { get; set; }
public string SerializeForAuditLog()
{
return ToString();
}
public override string ToString()
{
return String.Format("{2} Successful:{0} #{1}", Successful, Time, GetType().Name);
}
}
#endregion
#region Attempt creation utility methods
public SecurityDelayManager.IAttempt CreateLoginAttempt(bool success, string userName)
{
return new LoginAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
public SecurityDelayManager.IAttempt CreatePasswordResetAttempt(bool success, string userName)
{
return new PasswordResetAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
public SecurityDelayManager.IAttempt CreatePasswordResetRequestAttempt(bool success, string userName)
{
return new PasswordResetRequestAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
#endregion
#region Implementation of SecurityDelayManager.IGate
public Guid AccountID { get { return myID; } }
public bool ConsiderSuccessfulAttemptsToo { get { return false; } }
public TimeSpan SecurityTimeFrame { get { return myTF; } }
public SecurityDelayManager.ActionResult Action(SecurityDelayManager.IAttempt attempt, int attemptsCount)
{
var delaySecs = Math.Pow(2, attemptsCount / 5);
if (delaySecs > 30)
{
return SecurityDelayManager.ActionResult.Emergency;
}
else if (delaySecs < 3)
{
return SecurityDelayManager.ActionResult.NotDelayed;
}
else
{
// TODO: Implement the security delay logic
return SecurityDelayManager.ActionResult.Delayed;
}
}
#endregion
}
Backend somewhat thread-safe management
So this class (in my core lib) will handle the multi-threaded counting of attempts:
/// <summary>
/// Helps to count attempts and take action with some thread safety
/// </summary>
public sealed class SecurityDelayManager
{
ILog log = LogManager.GetLogger(typeof(SecurityDelayManager).FullName + ".Log");
ILog audit = LogManager.GetLogger(typeof(SecurityDelayManager).FullName + ".Audit");
#region static
static SecurityDelayManager me = new SecurityDelayManager();
static Type igateType = typeof(IGate);
public static SecurityDelayManager Instance { get { return me; } }
#endregion
#region Types
public interface IAttempt
{
/// <summary>
/// Is this a successful attempt?
/// </summary>
bool Successful { get; }
/// <summary>
/// When did this happen
/// </summary>
DateTime Time { get; }
String SerializeForAuditLog();
}
/// <summary>
/// Gate represents an entry point at wich an attempt was made
/// </summary>
public interface IGate
{
/// <summary>
/// Uniquely identifies the gate
/// </summary>
Guid AccountID { get; }
/// <summary>
/// Besides unsuccessful attempts, successful attempts too introduce security delay
/// </summary>
bool ConsiderSuccessfulAttemptsToo { get; }
TimeSpan SecurityTimeFrame { get; }
ActionResult Action(IAttempt attempt, int attemptsCount);
}
public enum ActionResult { NotDelayed, Delayed, Emergency }
public class SecurityActionEventArgs : EventArgs
{
public SecurityActionEventArgs(IGate gate, int attemptCount, IAttempt attempt, ActionResult result)
{
Gate = gate; AttemptCount = attemptCount; Attempt = attempt; Result = result;
}
public ActionResult Result { get; private set; }
public IGate Gate { get; private set; }
public IAttempt Attempt { get; private set; }
public int AttemptCount { get; private set; }
}
#endregion
#region Fields
Dictionary<Guid, Queue<IAttempt>> attempts = new Dictionary<Guid, Queue<IAttempt>>();
Dictionary<Type, IGate> gates = new Dictionary<Type, IGate>();
#endregion
#region Events
public event EventHandler<SecurityActionEventArgs> SecurityAction;
#endregion
/// <summary>
/// private (hidden) constructor, only static instance access (singleton)
/// </summary>
private SecurityDelayManager() { }
/// <summary>
/// Look at the attempt and the history for a given gate, let the gate take action on the findings
/// </summary>
/// <param name="gate"></param>
/// <param name="attempt"></param>
public ActionResult Check(IGate gate, IAttempt attempt)
{
if (gate == null) throw new ArgumentException("gate");
if (attempt == null) throw new ArgumentException("attempt");
// get the input data befor we lock(queue)
var cleanupTime = DateTime.Now.Subtract(gate.SecurityTimeFrame);
var considerSuccessful = gate.ConsiderSuccessfulAttemptsToo;
var attemptSuccessful = attempt.Successful;
int attemptsCount; // = ?
// not caring too much about threads here as risks are low
Queue<IAttempt> queue = attempts.ContainsKey(gate.AccountID)
? attempts[gate.AccountID]
: attempts[gate.AccountID] = new Queue<IAttempt>();
// thread sensitive - keep it local and short
lock (queue)
{
// maintenance first
while (queue.Count != 0 && queue.Peek().Time < cleanupTime)
{
queue.Dequeue();
}
// enqueue attempt if necessary
if (!attemptSuccessful || considerSuccessful)
{
queue.Enqueue(attempt);
}
// get the queue length
attemptsCount = queue.Count;
}
// let the gate decide what now...
var result = gate.Action(attempt, attemptsCount);
// audit log
switch (result)
{
case ActionResult.Emergency:
audit.ErrorFormat("{0}: Emergency! Attempts count: {1}. {2}", gate, attemptsCount, attempt.SerializeForAuditLog());
break;
case ActionResult.Delayed:
audit.WarnFormat("{0}: Delayed. Attempts count: {1}. {2}", gate, attemptsCount, attempt.SerializeForAuditLog());
break;
default:
audit.DebugFormat("{0}: {3}. Attempts count: {1}. {2}", gate, attemptsCount, attempt.SerializeForAuditLog(), result);
break;
}
// notification
if (SecurityAction != null)
{
var ea = new SecurityActionEventArgs(gate, attemptsCount, attempt, result);
SecurityAction(this, ea);
}
return result;
}
public void ResetAttempts()
{
attempts.Clear();
}
#region Gates access
public TGate GetGate<TGate>() where TGate : IGate, new()
{
var t = typeof(TGate);
return (TGate)GetGate(t);
}
public IGate GetGate(Type gateType)
{
if (gateType == null) throw new ArgumentNullException("gateType");
if (!igateType.IsAssignableFrom(gateType)) throw new Exception("Provided gateType is not of IGate");
if (!gates.ContainsKey(gateType) || gates[gateType] == null)
gates[gateType] = (IGate)Activator.CreateInstance(gateType);
return gates[gateType];
}
/// <summary>
/// Set a specific instance of a gate for a type
/// </summary>
/// <typeparam name="TGate"></typeparam>
/// <param name="gate">can be null to reset the gate for that TGate</param>
public void SetGate<TGate>(TGate gate) where TGate : IGate
{
var t = typeof(TGate);
SetGate(t, gate);
}
/// <summary>
/// Set a specific instance of a gate for a type
/// </summary>
/// <param name="gateType"></param>
/// <param name="gate">can be null to reset the gate for that gateType</param>
public void SetGate(Type gateType, IGate gate)
{
if (gateType == null) throw new ArgumentNullException("gateType");
if (!igateType.IsAssignableFrom(gateType)) throw new Exception("Provided gateType is not of IGate");
gates[gateType] = gate;
}
#endregion
}
Tests
And I've made a test fixture for that:
[TestFixture]
public class SecurityDelayManagerTest
{
static MyTestLoginGate gate;
static SecurityDelayManager manager;
[SetUp]
public void TestSetUp()
{
manager = SecurityDelayManager.Instance;
gate = new MyTestLoginGate();
manager.SetGate(gate);
}
[TearDown]
public void TestTearDown()
{
manager.ResetAttempts();
}
[Test]
public void Test_SingleFailedAttemptCheck()
{
var attempt = gate.CreateLoginAttempt(false, "user1");
Assert.IsNotNull(attempt);
manager.Check(gate, attempt);
Assert.AreEqual(1, gate.AttemptsCount);
}
[Test]
public void Test_AttemptExpiration()
{
var attempt = gate.CreateLoginAttempt(false, "user1");
Assert.IsNotNull(attempt);
manager.Check(gate, attempt);
Assert.AreEqual(1, gate.AttemptsCount);
}
[Test]
public void Test_SingleSuccessfulAttemptCheck()
{
var attempt = gate.CreateLoginAttempt(true, "user1");
Assert.IsNotNull(attempt);
manager.Check(gate, attempt);
Assert.AreEqual(0, gate.AttemptsCount);
}
[Test]
public void Test_ManyAttemptChecks()
{
for (int i = 0; i < 20; i++)
{
var attemptGood = gate.CreateLoginAttempt(true, "user1");
manager.Check(gate, attemptGood);
var attemptBaad = gate.CreateLoginAttempt(false, "user1");
manager.Check(gate, attemptBaad);
}
Assert.AreEqual(20, gate.AttemptsCount);
}
[Test]
public void Test_GateAccess()
{
Assert.AreEqual(gate, manager.GetGate<MyTestLoginGate>(), "GetGate should keep the same gate");
Assert.AreEqual(gate, manager.GetGate(typeof(MyTestLoginGate)), "GetGate should keep the same gate");
manager.SetGate<MyTestLoginGate>(null);
var oldGate = gate;
var newGate = manager.GetGate<MyTestLoginGate>();
gate = newGate;
Assert.AreNotEqual(oldGate, newGate, "After a reset, new gate should be created");
manager.ResetAttempts();
Test_ManyAttemptChecks();
manager.SetGate(typeof(MyTestLoginGate), oldGate);
manager.ResetAttempts();
Test_ManyAttemptChecks();
}
}
public class MyTestLoginGate : SecurityDelayManager.IGate
{
#region Static
static Guid myID = new Guid("81e19a1d-a8ec-4476-a187-5130361a9006");
static TimeSpan myTF = TimeSpan.FromHours(24);
class LoginAttempt : Attempt { }
class PasswordResetAttempt : Attempt { }
abstract class Attempt : SecurityDelayManager.IAttempt
{
public bool Successful { get; set; }
public DateTime Time { get; set; }
public String UserName { get; set; }
public string SerializeForAuditLog()
{
return ToString();
}
public override string ToString()
{
return String.Format("Attempt {2} Successful:{0} #{1}", Successful, Time, GetType().Name);
}
}
#endregion
#region Test properties
public int AttemptsCount { get; private set; }
#endregion
#region Implementation of SecurityDelayManager.IGate
public Guid AccountID { get { return myID; } }
public bool ConsiderSuccessfulAttemptsToo { get { return false; } }
public TimeSpan SecurityTimeFrame { get { return myTF; } }
public SecurityDelayManager.IAttempt CreateLoginAttempt(bool success, string userName)
{
return new LoginAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
public SecurityDelayManager.IAttempt CreatePasswordResetAttempt(bool success, string userName)
{
return new PasswordResetAttempt() { Successful = success, UserName = userName, Time = DateTime.Now };
}
public SecurityDelayManager.ActionResult Action(SecurityDelayManager.IAttempt attempt, int attemptsCount)
{
AttemptsCount = attemptsCount;
return attemptsCount < 3
? SecurityDelayManager.ActionResult.NotDelayed
: attemptsCount < 30
? SecurityDelayManager.ActionResult.Delayed
: SecurityDelayManager.ActionResult.Emergency;
}
#endregion
}
I would place the delay on the server validation portion where it won't attempt to validate (come back automatically as false have a message saying the user has to wait so many seconds before making another attempt). another answer until so many seconds have passed. Doing the thread.sleep will prevent one browser from making another attempt, but it won't stop a distributed attack where someone has multiple programs trying to login as the user simultaneously.
Another possibility is that the time between attempts varies by how many attempts are made to login. So the second attempt they have a one second wait, the third is maybe 2, the third is 4 and so on. That way you don't have a legitimate user having to wait 15 seconds between login attempts because they mistyped their password incorrectly the first time.
Kevin makes a good point about not wanting to tie up your request thread. One answer would be to make the login an asychronous request. The asychronous process would just be to wait for the amount of time you choose (500ms?). Then you wouldn't block the request thread.
I don't think this will help you thwart DOS attacks. If you sleep the request thread, you are still allowing the request to occupy your thread pool and still allow the attacker to bring your web service to its knees.
Your best bet may be to lock out requests after a specified number of failed attempts based on the attempted login name, source IP, etc, to try and target the source of the attack without detriment to your valid users.
I know it's not what you're asking, but you could implement an account lockout instead. That way, you give them their guesses and then can make them wait any amount of time you want before they can start guessing again. :)
I don't think what you are asking for is quite an efficient way in a web enviornment. Login screens' purpose is to provide an easy way for 'users' to gain access to your services and should be easy and fast to use. So you should not make a user wait considering 99% of the them will not be bad-minded.
Sleep.Trhead also has the potential to place a huge load on your server should there be a lot of concurrent users trying to log in. Potential options would be:
Block the IP for (e.g.) the end of the session for x number of unsuccessful login attempts
Provide a captcha
of course these are not all the options but still I am sure more people will have more ideas...
I'm doing a custom 404 page for a large website that's undergoing a redesign. There are about 40 high-use pages that customers may have bookmarked, and our new site structure will break these bookmarks.
On my custom 404 page, I want to alert them to the new URL if they attempt to navigate to one of these high-use pages via its old URL. So I have a couple of dynamic controls on the 404 page, one for a "did-you-want-this?" type of dialog, and another for a "if-so-go-here (and update your bookmark)" type of dialog. That's the easy part.
To suggest a new URL, I'm looking at the requested URL. If it has key words in it, I'm going to suggest the new URL based on that, and them I'm firing off the appropriate did-you-want..., and if-so... suggestions on the 404 page as mentioned above.
So I want to store these 40-ish key/value pairs (keyword/new-URL pairs) in a data structure, and I'm not sure what would be best. Dictionary? IDictionary? What's the difference and which is more appropriate?
Or am I totally on the wrong track?
Thanks for your time.
I would use the Dictionary<T,T> from the System.Collections.Generic namespace.
You could use NameValueCollection.
Maybe this is overkill for your use case, but I'd probably allow for multiple keywords per Uri, and a relative weight score. Then, dynamically score the keywords that match.
class UriSuggester {
private List<SuggestedUri> Uris { get; set; }
Uri[] GetSuggestions(Uri originalUri) {
var suggestionHits = new Dictionary<SuggestedUri, int>();
foreach (var keyword in KeyWords.Parse(originalUri)) {
// find suggestions matching that keyword
foreach (var suggestedUri in Uris.Where(u => u.Keywords.Contains(keyword)) {
// add a hit for keyword match
suggestionHits[suggestedUri] += 1;
}
}
// order by weight * hits
return suggestionHits.Keys
.OrderBy(s => s.Weight * suggestionHits[s])
.Select(s => s.Uri)
.ToArray();
}
}
class SuggestedUri {
public Uri Suggested { get; set; }
public int Weight { get; set; }
public Keyword[] Keywords;
}
class Keyword {
public string Value { get; set; }
public static Keyword[] Parse(Uri uri);
override Equals;
override GetHashCode;
}
Dictionary would be fine. Wether you store it as the interface type IDictionary or Dictionary itself wouldn't matter much in this case as it's not going to be passed much around, besides on the 404 page itself.
Have you considered doing some URL rewriting to still support the old URLs?
You can consider writing your own class logic and then assign that to a List data structure as following:
public class KeyValuesClass
{
private string a_key;
private string a_value;
public KeyValuesClass(string a_key, string a_value)
{
this.a_key = a_key;
this.a_value = a_value;
}
public string Key
{
get{ return a_key; }
set { a_key = value; }
}
public string Value
{
get{ return a_value; }
set { a_value = value; }
}
}
somewhere in the code
List<KeyValuesClass> my_key_value_list = new List<KeyValuesClass>();
my_key_value_list.Add(new KeyValuesClass("key", "value");
But you can consider Dictionary as our fellow programmer mentioned it above :)