PageRenderTime 137ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/Visigo.Sharepoint.FormsBasedAuthentication/Visigo.Sharepoint.FormsBasedAuthentication/Layouts/FBA/ImageHipChallenge.ashx

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