PageRenderTime 24ms CodeModel.GetById 15ms app.highlight 5ms 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" %>
  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}