PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/BlogEngine/BlogEngine.NET/App_Code/Extensions/Recaptcha/RecaptchaControl.cs

#
C# | 619 lines | 386 code | 73 blank | 160 comment | 25 complexity | 5da3ecdda507a1cbb6b2075551288858 MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, BSD-3-Clause
  1. // Copyright (c) 2007 Adrian Godong, Ben Maurer
  2. // Permission is hereby granted, free of charge, to any person obtaining a copy
  3. // of this software and associated documentation files (the "Software"), to deal
  4. // in the Software without restriction, including without limitation the rights
  5. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  6. // copies of the Software, and to permit persons to whom the Software is
  7. // furnished to do so, subject to the following conditions:
  8. // The above copyright notice and this permission notice shall be included in
  9. // all copies or substantial portions of the Software.
  10. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  11. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  12. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  13. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  14. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  15. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  16. // THE SOFTWARE.
  17. // Adapted for dotnetblogengine by Filip Stanek ( http://www.bloodforge.com )
  18. namespace App_Code.Controls
  19. {
  20. using System;
  21. using System.Web;
  22. using System.Web.Caching;
  23. using System.Web.UI;
  24. using System.Web.UI.WebControls;
  25. using BlogEngine.Core;
  26. using BlogEngine.Core.Web.Extensions;
  27. using Recaptcha;
  28. /// <summary>
  29. /// The recaptcha control.
  30. /// </summary>
  31. public class RecaptchaControl : WebControl, IValidator
  32. {
  33. #region Constants and Fields
  34. /// <summary>
  35. /// The recaptcha challenge field.
  36. /// </summary>
  37. private const string RecaptchaChallengeField = "recaptcha_challenge_field";
  38. /*
  39. /// <summary>
  40. /// The recaptcha host.
  41. /// </summary>
  42. private const string RecaptchaHost = "http://api.recaptcha.net";
  43. */
  44. /// <summary>
  45. /// The recaptcha response field.
  46. /// </summary>
  47. private const string RecaptchaResponseField = "recaptcha_response_field";
  48. /*
  49. /// <summary>
  50. /// The recaptcha secure host.
  51. /// </summary>
  52. private const string RecaptchaSecureHost = "https://api-secure.recaptcha.net";
  53. */
  54. /// <summary>
  55. /// The error message.
  56. /// </summary>
  57. private string errorMessage;
  58. /// <summary>
  59. /// The private key.
  60. /// </summary>
  61. private string privateKey;
  62. /// <summary>
  63. /// The public key.
  64. /// </summary>
  65. private string publicKey;
  66. /// <summary>
  67. /// The recaptcha response.
  68. /// </summary>
  69. private RecaptchaResponse recaptchaResponse;
  70. /// <summary>
  71. /// The skip recaptcha.
  72. /// </summary>
  73. private bool skipRecaptcha = true;
  74. #endregion
  75. #region Properties
  76. /// <summary>
  77. /// Gets or sets ErrorMessage.
  78. /// </summary>
  79. public string ErrorMessage
  80. {
  81. get
  82. {
  83. return this.errorMessage ?? "The verification words are incorrect.";
  84. }
  85. set
  86. {
  87. this.errorMessage = value;
  88. }
  89. }
  90. /// <summary>
  91. /// Gets or sets a value indicating whether IsValid.
  92. /// </summary>
  93. public bool IsValid
  94. {
  95. get
  96. {
  97. return this.recaptchaResponse != null && this.recaptchaResponse.IsValid;
  98. }
  99. set
  100. {
  101. }
  102. }
  103. /// <summary>
  104. /// Gets MaxLogEntries.
  105. /// </summary>
  106. public int MaxLogEntries
  107. {
  108. get
  109. {
  110. var settings = ExtensionManager.GetSettings("Recaptcha");
  111. return Convert.ToInt32(settings.GetSingleValue("MaxLogEntries"));
  112. }
  113. }
  114. /// <summary>
  115. /// Gets a value indicating whether the control has been enabled via the Extension Manager
  116. /// </summary>
  117. public bool RecaptchaEnabled
  118. {
  119. get
  120. {
  121. var captchaExtension = ExtensionManager.GetExtension("Recaptcha");
  122. return captchaExtension.Enabled;
  123. }
  124. }
  125. /// <summary>
  126. /// Gets a value indicating whether RecaptchaLoggingEnabled.
  127. /// </summary>
  128. public bool RecaptchaLoggingEnabled
  129. {
  130. get
  131. {
  132. ExtensionManager.GetSettings("Recaptcha");
  133. return this.MaxLogEntries > 0;
  134. }
  135. }
  136. /// <summary>
  137. /// Gets a value indicating whether the recaptcha needs to be displayed for the current user
  138. /// </summary>
  139. public bool RecaptchaNecessary
  140. {
  141. get
  142. {
  143. var settings = ExtensionManager.GetSettings("Recaptcha");
  144. return !Security.IsAuthenticated ||
  145. Convert.ToBoolean(settings.GetSingleValue("ShowForAuthenticatedUsers"));
  146. }
  147. }
  148. /// <summary>
  149. /// Gets or sets Theme.
  150. /// </summary>
  151. public string Theme { get; set; }
  152. /// <summary>
  153. /// Gets or sets Language.
  154. /// </summary>
  155. public string Language { get; set; }
  156. /// <summary>
  157. /// Gets or sets UserUniqueIdentifier.
  158. /// </summary>
  159. public string UserUniqueIdentifier { get; set; }
  160. /// <summary>
  161. /// Gets or sets PageLoadTime.
  162. /// </summary>
  163. internal DateTime PageLoadTime
  164. {
  165. get
  166. {
  167. return Blog.CurrentInstance.Cache[string.Format("{0}PageLoadTime", this.UserUniqueIdentifier)] != null
  168. ? Convert.ToDateTime(
  169. Blog.CurrentInstance.Cache[string.Format("{0}PageLoadTime", this.UserUniqueIdentifier)])
  170. : DateTime.Now;
  171. }
  172. set
  173. {
  174. if (Blog.CurrentInstance.Cache[string.Format("{0}PageLoadTime", this.UserUniqueIdentifier)] != null)
  175. {
  176. Blog.CurrentInstance.Cache[string.Format("{0}PageLoadTime", this.UserUniqueIdentifier)] = value;
  177. }
  178. else
  179. {
  180. Blog.CurrentInstance.Cache.Add(
  181. string.Format("{0}PageLoadTime", this.UserUniqueIdentifier),
  182. value,
  183. null,
  184. Cache.NoAbsoluteExpiration,
  185. new TimeSpan(1, 0, 0),
  186. CacheItemPriority.Low,
  187. null);
  188. }
  189. }
  190. }
  191. /// <summary>
  192. /// Gets or sets RecaptchaAttempts.
  193. /// </summary>
  194. internal ushort RecaptchaAttempts
  195. {
  196. get
  197. {
  198. return Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaAttempts", this.UserUniqueIdentifier)] !=
  199. null
  200. ? Convert.ToUInt16(
  201. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaAttempts", this.UserUniqueIdentifier)])
  202. : (ushort)0;
  203. }
  204. set
  205. {
  206. if (Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaAttempts", this.UserUniqueIdentifier)] != null)
  207. {
  208. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaAttempts", this.UserUniqueIdentifier)] = value;
  209. }
  210. else
  211. {
  212. Blog.CurrentInstance.Cache.Add(
  213. string.Format("{0}RecaptchaAttempts", this.UserUniqueIdentifier),
  214. value,
  215. null,
  216. Cache.NoAbsoluteExpiration,
  217. new TimeSpan(0, 15, 0),
  218. CacheItemPriority.Low,
  219. null);
  220. }
  221. }
  222. }
  223. /// <summary>
  224. /// Gets or sets RecaptchaChallengeValue.
  225. /// </summary>
  226. internal string RecaptchaChallengeValue
  227. {
  228. get
  229. {
  230. return
  231. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaChallengeValue", this.UserUniqueIdentifier)] !=
  232. null
  233. ? Convert.ToString(
  234. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaChallengeValue", this.UserUniqueIdentifier)])
  235. : string.Empty;
  236. }
  237. set
  238. {
  239. if (Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaChallengeValue", this.UserUniqueIdentifier)] !=
  240. null)
  241. {
  242. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaChallengeValue", this.UserUniqueIdentifier)] =
  243. value;
  244. }
  245. else
  246. {
  247. Blog.CurrentInstance.Cache.Add(
  248. string.Format("{0}RecaptchaChallengeValue", this.UserUniqueIdentifier),
  249. value,
  250. null,
  251. Cache.NoAbsoluteExpiration,
  252. new TimeSpan(0, 1, 0),
  253. CacheItemPriority.NotRemovable,
  254. null);
  255. }
  256. }
  257. }
  258. /// <summary>
  259. /// Gets or sets RecaptchaRenderTime.
  260. /// </summary>
  261. internal DateTime RecaptchaRenderTime
  262. {
  263. get
  264. {
  265. return Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaRenderTime", this.UserUniqueIdentifier)] !=
  266. null
  267. ? Convert.ToDateTime(
  268. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaRenderTime", this.UserUniqueIdentifier)])
  269. : DateTime.Now;
  270. }
  271. set
  272. {
  273. if (Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaRenderTime", this.UserUniqueIdentifier)] !=
  274. null)
  275. {
  276. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaRenderTime", this.UserUniqueIdentifier)] =
  277. value;
  278. }
  279. else
  280. {
  281. Blog.CurrentInstance.Cache.Add(
  282. string.Format("{0}RecaptchaRenderTime", this.UserUniqueIdentifier),
  283. value,
  284. null,
  285. Cache.NoAbsoluteExpiration,
  286. new TimeSpan(1, 0, 0),
  287. CacheItemPriority.Low,
  288. null);
  289. }
  290. }
  291. }
  292. /// <summary>
  293. /// Gets or sets RecaptchaResponseValue.
  294. /// </summary>
  295. internal string RecaptchaResponseValue
  296. {
  297. get
  298. {
  299. return
  300. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaResponseValue", this.UserUniqueIdentifier)] !=
  301. null
  302. ? Convert.ToString(
  303. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaResponseValue", this.UserUniqueIdentifier)])
  304. : string.Empty;
  305. }
  306. set
  307. {
  308. if (Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaResponseValue", this.UserUniqueIdentifier)] !=
  309. null)
  310. {
  311. Blog.CurrentInstance.Cache[string.Format("{0}RecaptchaResponseValue", this.UserUniqueIdentifier)] =
  312. value;
  313. }
  314. else
  315. {
  316. Blog.CurrentInstance.Cache.Add(
  317. string.Format("{0}RecaptchaResponseValue", this.UserUniqueIdentifier),
  318. value,
  319. null,
  320. Cache.NoAbsoluteExpiration,
  321. new TimeSpan(0, 1, 0),
  322. CacheItemPriority.NotRemovable,
  323. null);
  324. }
  325. }
  326. }
  327. #endregion
  328. #region Public Methods
  329. /// <summary>
  330. /// The update log.
  331. /// </summary>
  332. /// <param name="comment">
  333. /// The comment.
  334. /// </param>
  335. public void UpdateLog(Comment comment)
  336. {
  337. if (!this.RecaptchaLoggingEnabled || this.skipRecaptcha)
  338. {
  339. return;
  340. }
  341. var log = RecaptchaLogger.ReadLogItems();
  342. var logItem = new RecaptchaLogItem
  343. {
  344. Response = this.RecaptchaResponseValue,
  345. Challenge = this.RecaptchaChallengeValue,
  346. CommentId = comment.Id,
  347. Enabled = this.RecaptchaEnabled,
  348. Necessary = this.RecaptchaNecessary,
  349. NumberOfAttempts = this.RecaptchaAttempts,
  350. TimeToComment = DateTime.Now.Subtract(this.PageLoadTime).TotalSeconds,
  351. TimeToSolveCapcha = DateTime.Now.Subtract(this.RecaptchaRenderTime).TotalSeconds
  352. };
  353. log.Add(logItem);
  354. if (log.Count > this.MaxLogEntries)
  355. {
  356. log.RemoveRange(0, log.Count - this.MaxLogEntries);
  357. }
  358. RecaptchaLogger.SaveLogItems(log);
  359. this.RecaptchaAttempts = 0;
  360. this.PageLoadTime = DateTime.Now;
  361. Blog.CurrentInstance.Cache.Remove(string.Format("{0}RecaptchaChallengeValue", this.UserUniqueIdentifier));
  362. Blog.CurrentInstance.Cache.Remove(string.Format("{0}RecaptchaResponseValue", this.UserUniqueIdentifier));
  363. }
  364. /// <summary>
  365. /// Validates the async.
  366. /// </summary>
  367. /// <param name="response">
  368. /// The response.
  369. /// </param>
  370. /// <param name="challenge">
  371. /// The challenge.
  372. /// </param>
  373. /// <returns>
  374. /// Whether valid.
  375. /// </returns>
  376. public bool ValidateAsync(string response, string challenge)
  377. {
  378. if (this.RecaptchaLoggingEnabled)
  379. {
  380. this.RecaptchaAttempts++;
  381. }
  382. this.RecaptchaResponseValue = response;
  383. this.RecaptchaChallengeValue = challenge;
  384. this.Validate();
  385. return this.IsValid;
  386. }
  387. #endregion
  388. #region Implemented Interfaces
  389. #region IValidator
  390. /// <summary>
  391. /// The validate.
  392. /// </summary>
  393. public void Validate()
  394. {
  395. if (this.skipRecaptcha)
  396. {
  397. this.recaptchaResponse = RecaptchaResponse.Valid;
  398. }
  399. else
  400. {
  401. var validator = new RecaptchaValidator
  402. {
  403. PrivateKey = this.privateKey, RemoteIP = this.Page.Request.UserHostAddress
  404. };
  405. if (String.IsNullOrEmpty(this.RecaptchaChallengeValue) &&
  406. String.IsNullOrEmpty(this.RecaptchaResponseValue))
  407. {
  408. validator.Challenge = this.Context.Request.Form[RecaptchaChallengeField];
  409. validator.Response = this.Context.Request.Form[RecaptchaResponseField];
  410. }
  411. else
  412. {
  413. validator.Challenge = this.RecaptchaChallengeValue;
  414. validator.Response = this.RecaptchaResponseValue;
  415. }
  416. this.recaptchaResponse = validator.Validate();
  417. }
  418. }
  419. #endregion
  420. #endregion
  421. #region Methods
  422. /// <summary>
  423. /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event.
  424. /// </summary>
  425. /// <param name="e">
  426. /// An <see cref="T:System.EventArgs"/> object that contains the event data.
  427. /// </param>
  428. protected override void OnInit(EventArgs e)
  429. {
  430. base.OnInit(e);
  431. var settings = ExtensionManager.GetSettings("Recaptcha");
  432. this.publicKey = settings.GetSingleValue("PublicKey");
  433. this.privateKey = settings.GetSingleValue("PrivateKey");
  434. if (String.IsNullOrEmpty(this.Theme))
  435. {
  436. this.Theme = settings.GetSingleValue("Theme");
  437. }
  438. if (String.IsNullOrEmpty(this.Language))
  439. {
  440. this.Language = settings.GetSingleValue("Language");
  441. }
  442. if (this.RecaptchaEnabled && this.RecaptchaNecessary)
  443. {
  444. this.skipRecaptcha = false;
  445. }
  446. if (String.IsNullOrEmpty(this.publicKey) || String.IsNullOrEmpty(this.privateKey))
  447. {
  448. throw new ApplicationException("reCAPTCHA needs to be configured with a public & private key.");
  449. }
  450. }
  451. /// <summary>
  452. /// Raises the <see cref="E:System.Web.UI.Control.Unload"/> event.
  453. /// </summary>
  454. /// <param name="e">
  455. /// An <see cref="T:System.EventArgs"/> object that contains event data.
  456. /// </param>
  457. protected override void OnUnload(EventArgs e)
  458. {
  459. if (this.RecaptchaLoggingEnabled)
  460. {
  461. if (!this.Page.IsCallback)
  462. {
  463. this.PageLoadTime = DateTime.Now;
  464. this.RecaptchaAttempts = 0;
  465. }
  466. this.RecaptchaRenderTime = DateTime.Now;
  467. }
  468. base.OnUnload(e);
  469. }
  470. /// <summary>
  471. /// Renders the control to the specified HTML writer.
  472. /// </summary>
  473. /// <param name="writer">
  474. /// The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the control content.
  475. /// </param>
  476. protected override void Render(HtmlTextWriter writer)
  477. {
  478. if (!this.skipRecaptcha)
  479. {
  480. this.RenderContents(writer);
  481. }
  482. }
  483. /// <summary>
  484. /// Renders the contents.
  485. /// </summary>
  486. /// <param name="output">
  487. /// The output.
  488. /// </param>
  489. protected override void RenderContents(HtmlTextWriter output)
  490. {
  491. output.AddAttribute("type", "text/javascript");
  492. output.AddAttribute("src", "http://api.recaptcha.net/js/recaptcha_ajax.js");
  493. output.RenderBeginTag("script");
  494. output.RenderEndTag();
  495. output.AddAttribute(HtmlTextWriterAttribute.Id, "spnCaptchaIncorrect");
  496. output.AddAttribute(HtmlTextWriterAttribute.Style, "color:Red;display:none;");
  497. output.RenderBeginTag("span");
  498. output.WriteLine("The captcha text was not valid. Please try again.");
  499. output.RenderEndTag();
  500. output.AddAttribute(HtmlTextWriterAttribute.Id, "recaptcha_placeholder");
  501. output.RenderBeginTag(HtmlTextWriterTag.Div);
  502. output.RenderEndTag();
  503. output.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
  504. output.RenderBeginTag(HtmlTextWriterTag.Script);
  505. output.WriteLine("function showRecaptcha() {");
  506. output.WriteLine("Recaptcha.create('{0}', 'recaptcha_placeholder', {{", this.publicKey);
  507. output.WriteLine("theme: '{0}',", this.Theme);
  508. output.WriteLine("lang: '{0}',", this.Language);
  509. output.WriteLine("tabindex: {0}", this.TabIndex);
  510. output.WriteLine("})");
  511. output.WriteLine("}");
  512. output.WriteLine("var rc_oldonload = window.onload;");
  513. output.WriteLine("if (typeof window.onload != 'function') {");
  514. output.WriteLine("window.onload = showRecaptcha;");
  515. output.WriteLine("}");
  516. output.WriteLine("else {");
  517. output.WriteLine("window.onload = function() {");
  518. output.WriteLine("rc_oldonload();");
  519. output.WriteLine("showRecaptcha();");
  520. output.WriteLine("}");
  521. output.WriteLine("}");
  522. output.RenderEndTag();
  523. }
  524. #endregion
  525. /*
  526. /// <summary>
  527. /// Generates the challenge URL.
  528. /// </summary>
  529. /// <param name="noscript">if set to <c>true</c> [no script].</param>
  530. /// <returns>The challenge url.</returns>
  531. private string GenerateChallengeUrl(bool noscript)
  532. {
  533. var urlBuilder = new StringBuilder();
  534. urlBuilder.Append(this.Context.Request.IsSecureConnection ? RecaptchaSecureHost : RecaptchaHost);
  535. urlBuilder.Append(noscript ? "/noscript?" : "/challenge?");
  536. urlBuilder.AppendFormat("k={0}", this.publicKey);
  537. if (this.recaptchaResponse != null && this.recaptchaResponse.ErrorCode != string.Empty)
  538. {
  539. urlBuilder.AppendFormat("&error={0}", this.recaptchaResponse.ErrorCode);
  540. }
  541. return urlBuilder.ToString();
  542. }
  543. */
  544. }
  545. }