/Visigo.Sharepoint.FormsBasedAuthentication/Visigo.Sharepoint.FormsBasedAuthentication/Layouts/FBA/ImageHipChallenge.ashx
Unknown | 223 lines | 202 code | 21 blank | 0 comment | 0 complexity | 9a012f48394fc7ea39db1fd394127ba4 MD5 | raw file
1<%@ WebHandler Language="C#" Class="Visigo.Sharepoint.FormsBasedAuthentication.HIP.ImageHipChallengeHandler" %> 2using System; 3using System.Web; 4using System.Drawing; 5using System.Drawing.Imaging; 6using System.Drawing.Drawing2D; 7using System.Collections.Specialized; 8using System.Security.Cryptography; 9 10namespace Visigo.Sharepoint.FormsBasedAuthentication.HIP 11{ 12 /// <summary>Provides cryptographically-strong pseudo-random numbers.</summary> 13 internal class RandomNumbers 14 { 15 /// <summary>Random number generator.</summary> 16 private RNGCryptoServiceProvider _rand = new RNGCryptoServiceProvider(); 17 18 /// <summary>Used by NextRandom and NextRandomDouble.</summary> 19 /// <remarks> 20 /// RNGCryptoServiceProvider is not thread-safe. Therefore, we only call to on a single thread. 21 /// As a result, we can resuse the same byte arrays for every call. 22 /// </remarks> 23 private byte[] _rd4 = new byte[4], _rd8 = new byte[8]; 24 25 /// <summary>Gets a random non-negative integer less than max.</summary> 26 /// <param name="max">The upper-bound for the random number.</param> 27 /// <returns>The generated random number.</returns> 28 public int Next(int max) 29 { 30 if (max <= 0) throw new ArgumentOutOfRangeException("max"); 31 _rand.GetBytes(_rd4); 32 int val = BitConverter.ToInt32(_rd4, 0) % max; 33 if (val < 0) val = -val; 34 return val; 35 } 36 37 /// <summary>Gets a random number between min and max, inclusive.</summary> 38 /// <param name="min">The minimum possible value.</param> 39 /// <param name="max">The maximum possible value.</param> 40 /// <returns>The randomly generated number.</returns> 41 public int Next(int min, int max) 42 { 43 if (min > max) throw new ArgumentOutOfRangeException("max"); 44 return Next(max - min + 1) + min; 45 } 46 47 /// <summary>Gets a randomly generated double between 0.0 and 1.1.</summary> 48 /// <returns>The random number.</returns> 49 public double NextDouble() 50 { 51 _rand.GetBytes(_rd8); 52 return BitConverter.ToUInt64(_rd8, 0) / (double)UInt64.MaxValue; 53 } 54 } 55 56 /// <summary>Handles requests for dynamic images from the ImageHipChallenge control.</summary> 57 public class ImageHipChallengeHandler : IHttpHandler 58 { 59 /// <summary>Default value for the RenderUrl property.</summary> 60 private const string RENDERURL_DEFAULT = "ImageHipChallenge.ashx"; 61 /// <summary>Query string key for the image width.</summary> 62 internal const string WIDTH_KEY = "w"; 63 /// <summary>Query string key for the image height.</summary> 64 internal const string HEIGHT_KEY = "h"; 65 /// <summary>Query string key for challenge ID.</summary> 66 internal const string ID_KEY = "id"; 67 private RandomNumbers _rand = new RandomNumbers(); 68 69 /// <summary>Maximum width of an image to generate.</summary> 70 private const int MAX_IMAGE_WIDTH = 600; 71 /// <summary>Maximum height of an image to generate.</summary> 72 private const int MAX_IMAGE_HEIGHT = 600; 73 74 /// <summary>Gets whether this handler is reusable.</summary> 75 /// <remarks>This handler is not thread-safe (uses non thread-safe member variables), so it is not reusable.</remarks> 76 public bool IsReusable { get { return false; } } 77 78 /// <summary>Gets a random non-negative integer less than max.</summary> 79 /// <param name="max">The upper-bound for the random number.</param> 80 /// <returns>The generated random number.</returns> 81 protected int NextRandom(int max) { return _rand.Next(max); } 82 83 /// <summary>Gets a random number between min and max, inclusive.</summary> 84 /// <param name="min">The minimum possible value.</param> 85 /// <param name="max">The maximum possible value.</param> 86 /// <returns>The randomly generated number.</returns> 87 protected int NextRandom(int min, int max) { return _rand.Next(min, max); } 88 89 /// <summary>Gets a randomly generated double between 0.0 and 1.1.</summary> 90 /// <returns>The random number.</returns> 91 protected double NextRandomDouble() { return _rand.NextDouble(); } 92 93 /// <summary>Gets the challenge text for a particular ID.</summary> 94 /// <param name="challengeId">The ID of the challenge text to retrieve.</param> 95 /// <returns>The text associated with the specified ID; null if no text exists.</returns> 96 internal static string GetChallengeText(Guid challengeId) 97 { 98 HttpContext ctx = HttpContext.Current; 99 return (string)ctx.Cache[challengeId.ToString()]; 100 } 101 102 /// <summary>Processes the image request and generates the appropriate image.</summary> 103 /// <param name="context">The current HttpContext.</param> 104 public void ProcessRequest(HttpContext context) 105 { 106 // Retrieve query parameters and challenge text 107 NameValueCollection queryString = context.Request.QueryString; 108 int width = Convert.ToInt32(queryString[WIDTH_KEY]); 109 if (width <= 0 || width > MAX_IMAGE_WIDTH) throw new ArgumentOutOfRangeException(WIDTH_KEY); 110 int height = Convert.ToInt32(queryString[HEIGHT_KEY]); 111 if (height <= 0 || height > MAX_IMAGE_HEIGHT) throw new ArgumentOutOfRangeException(HEIGHT_KEY); 112 string text = GetChallengeText(new Guid(queryString[ID_KEY])); 113 114 if (text != null) 115 { 116 // We successfully retrieved the information, so generate the image and send it to the client. 117 HttpResponse resp = context.Response; 118 resp.Clear(); 119 resp.ContentType = "image/jpeg"; 120 using (Bitmap bmp = GenerateImage(text, new Size(width, height))) 121 { 122 bmp.Save(resp.OutputStream, ImageFormat.Jpeg); 123 } 124 } 125 } 126 127 /// <summary>Generates the challenge image.</summary> 128 /// <param name="text">The text to be rendered into the image.</param> 129 /// <param name="size">The size of the image to generate.</param> 130 /// <returns>A dynamically-generated challenge image.</returns> 131 public Bitmap GenerateImage(string text, Size size) 132 { 133 // Create the new Bitmap of the specified size and render to it 134 Bitmap bmp = new Bitmap(size.Width, size.Height); 135 using (Graphics g = Graphics.FromImage(bmp)) 136 { 137 // Draw the background as a random linear gradient 138 using(Brush b = new LinearGradientBrush( 139 new Rectangle(0,0,size.Width,size.Height), 140 Color.FromArgb(NextRandom(256),NextRandom(256),NextRandom(256)), 141 Color.FromArgb(NextRandom(256),NextRandom(256),NextRandom(256)), 142 (float)(NextRandomDouble()*360),false)) 143 { 144 g.FillRectangle(b, 0, 0, bmp.Width, bmp.Height); 145 } 146 147 // Select a font family and create the default sized font. We then need to shrink 148 // the font size until the text fits. 149 FontFamily ff = _families[NextRandom(_families.Length)]; 150 int emSize = (int)(size.Width*2 / text.Length); 151 Font f = new Font(ff, emSize); 152 try 153 { 154 // Make sure that the font size we have will fit with the selected text. 155 SizeF measured = new SizeF(0,0); 156 SizeF workingSize = new SizeF(size.Width, size.Height); 157 while (emSize > 2 && 158 (measured = g.MeasureString(text, f)).Width > workingSize.Width || 159 measured.Height > workingSize.Height) 160 { 161 f.Dispose(); 162 f = new Font(ff, emSize -= 2); 163 } 164 165 // Select a color and draw the string into the center of the image 166 using(StringFormat fmt = new StringFormat()) 167 { 168 fmt.Alignment = fmt.LineAlignment = StringAlignment.Center; 169 using(Brush b = new LinearGradientBrush( 170 new Rectangle(0,0,size.Width/2,size.Height/2), 171 Color.FromArgb(NextRandom(256),NextRandom(256),NextRandom(256)), 172 Color.FromArgb(NextRandom(256),NextRandom(256),NextRandom(256)), 173 (float)(NextRandomDouble()*360),false)) 174 { 175 g.DrawString(text, f, b, new Rectangle(0,0,bmp.Width,bmp.Height), fmt); 176 } 177 } 178 } 179 finally 180 { 181 // Clean up 182 f.Dispose(); 183 } 184 } 185 186 // Distort the final image and return it. This distortion amount is fairly arbitrary. 187 DistortImage(bmp, NextRandom(5, 10) * (NextRandom(2) == 1 ? 1 : -1) ); 188 return bmp; 189 } 190 191 /// <summary>Distorts the image.</summary> 192 /// <param name="b">The image to be transformed.</param> 193 /// <param name="distortion">An amount of distortion.</param> 194 private static void DistortImage(Bitmap b, double distortion) 195 { 196 int width = b.Width, height = b.Height; 197 198 // Copy the image so that we're always using the original for source color 199 using (Bitmap copy = (Bitmap)b.Clone()) 200 { 201 // Iterate over every pixel 202 for (int y = 0; y < height; y++) 203 { 204 for (int x = 0; x < width; x++) 205 { 206 // Adds a simple wave 207 int newX = (int)(x + (distortion * Math.Sin(Math.PI * y / 64.0))); 208 int newY = (int)(y + (distortion * Math.Cos(Math.PI * x / 64.0))); 209 if (newX < 0 || newX >= width) newX = 0; 210 if (newY < 0 || newY >= height) newY = 0; 211 b.SetPixel(x, y, copy.GetPixel(newX, newY)); 212 } 213 } 214 } 215 } 216 217 /// <summary>List of fonts that can be used for rendering text.</summary> 218 /// <remarks>This list can be changed to include any families available on the current system.</remarks> 219 private static FontFamily [] _families = { 220 new FontFamily("Times New Roman") 221 }; 222 } 223}