using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.Security;
namespace CKS.FormsBasedAuthentication.HIP
{
/// The base class for all Reverse Turing Test challenges.
public abstract class HipChallenge : WebControl, INamingContainer
{
/// Default value for AlwaysGenerateNewChallenge.
private const bool ALWAYS_GEN_DEFAULT = true;
/// Default value for Expiration.
private const int EXPIRATION_DEFAULT = 120;
/// Stores the challenge ID associated for future requests.
private HtmlInputHidden _hiddenData;
/// The list of words that can be rendered.
private StringCollection _words;
/// Whether Authenticate has previously succeeded in this HttpRequest.
private bool _authenticated;
/// Backing store for Expiration.
private int _expiration = EXPIRATION_DEFAULT;
/// Random number generator used to create the HIP.
private RandomNumbers _rand = new RandomNumbers();
/// Gets a random non-negative integer less than max.
/// The upper-bound for the random number.
/// The generated random number.
protected int NextRandom(int max) { return _rand.Next(max); }
/// Gets a random number between min and max, inclusive.
/// The minimum possible value.
/// The maximum possible value.
/// The randomly generated number.
protected int NextRandom(int min, int max) { return _rand.Next(min, max); }
/// Gets a randomly generated double between 0.0 and 1.1.
/// The random number.
protected double NextRandomDouble() { return _rand.NextDouble(); }
/// Gets or sets the duration of time (seconds) a user has before the challenge expires.
/// The duration of time (seconds) a user has before the challenge expires.
[Category("Behavior")]
[Description("The duration of time (seconds) a user has before the challenge expires.")]
[DefaultValue(EXPIRATION_DEFAULT)]
public int Expiration
{
get { return _expiration; }
set { _expiration = value; }
}
/// Gets the list of words the control can use to create challenges.
/// The list of words the control can use to create images.
[EditorBrowsable(EditorBrowsableState.Never)]
public StringCollection Words
{
get
{
if (_words == null) _words = new StringCollection();
return _words;
}
set
{
_words = value;
}
}
/// Selects a word to use in an image.
/// The word to use in the challenge.
protected virtual string ChooseWord()
{
if (Words.Count == 0) throw new InvalidOperationException("No words available for challenge.");
return Words[NextRandom(Words.Count)];
}
/// Creates the hidden field control that stores the challenge ID.
protected override void CreateChildControls()
{
_hiddenData = new HtmlInputHidden();
_hiddenData.EnableViewState = false;
Controls.Add(_hiddenData);
base.CreateChildControls();
}
/// Generates a new image and fills in the dynamic image and hidden field appropriately.
/// Ignored.
protected sealed override void OnPreRender(EventArgs e)
{
// Gets a word for the challenge, associates it with a new ID, and stores it for the client
string content = ChooseWord();
Guid id = Guid.NewGuid();
SetChallengeText(id, content, DateTime.Now.AddSeconds(Expiration));
_hiddenData.Value = id.ToString("N");
// Generates a challenge based on the selected word/phrase.
RenderChallenge(id, content);
base.OnPreRender(e);
}
/// Gets the challenge text for a particular ID.
/// The ID of the challenge text to retrieve.
/// The text associated with the specified ID; null if no text exists.
internal static string GetChallengeText(Guid challengeId)
{
HttpContext ctx = HttpContext.Current;
return (string)ctx.Cache[challengeId.ToString()];
}
/// Sets the challenge text for a particular ID.
/// The ID of the challenge with which this text should be associated.
/// The text to store along with the challenge ID.
/// The expiration date fo the challenge.
internal static void SetChallengeText(Guid challengeId, string text, DateTime expiration)
{
HttpContext ctx = HttpContext.Current;
if (text == null) ctx.Cache.Remove(challengeId.ToString());
else ctx.Cache.Insert(challengeId.ToString(), text, null, expiration, System.Web.Caching.Cache.NoSlidingExpiration);
}
/// Authenticates user-supplied data against that retrieved using the challenge ID.
/// The user-supplied data.
/// Whether the user-supplied data matches that retrieved using the challenge ID.
internal bool Authenticate(string userData)
{
// We want to allow multiple authentication requests within the same HTTP request,
// so we can the result as a member variable of the class (non-static)
if (_authenticated == true) return _authenticated;
// If no authentication has happened previously, and if the user has supplied text,
// and if the ID is stored correctly in the page, and if the user text matches the challenge text,
// then set the challenge text, note that we've authenticated, and return true. Otherwise, failed authentication.
if (userData != null && userData.Length > 0 &&
_hiddenData != null && _hiddenData.Value != null && _hiddenData.Value.Length > 0)
{
try
{
Guid id = new Guid(_hiddenData.Value);
string text = GetChallengeText(id);
if (text != null && string.Compare(userData, text, true) == 0)
{
_authenticated = true;
SetChallengeText(id, null, DateTime.MinValue);
return true;
}
}
catch(FormatException){}
}
return false;
}
/// Generats the challenge and presents it to the user.
/// The ID of the challenge.
/// The content to render.
protected abstract void RenderChallenge(Guid id, string content);
}
}